`
酷的飞上天空
  • 浏览: 517753 次
  • 性别: Icon_minigender_1
  • 来自: 无锡
社区版块
存档分类
最新评论

Oauth简易服务器端ruby实现,仿新浪微博验证的方式

阅读更多

前段时间用ruby实现了新浪微博的简易Oauth的客户端,对aouth协议有了一个大概的了解。

完成服务器端的实现,纯属自己一个的加深学习aouth的想法,嘿嘿.  验证支持basic,oauth,xauth

 

接收下用到的controller

OauthController 负责对用户aouth验证和发放accessToken

Oauth_base_controller  所有需要aouth验证的controller的父类,对子类的所有方法进行权限验证

一个帮助类

OauthUtil 负责字符串的加密和拼接

 

OauthController提供三个对外方法:

request_token

authorize

access_token

具体方法含义,对应oauth验证的每个url。

具体代码如下

 

# coding: utf-8
#      HTTP 400 Bad Request
#          o Unsupported parameter
#          o Unsupported signature method
#          o Missing required parameter
#          o Duplicated OAuth Protocol Parameter
#     HTTP 401 Unauthorized
#          o Invalid Consumer Key
#          o Invalid / expired Token
#          o Invalid signature
#          o Invalid / used nonce

class OauthController < Oauth_base_controller
  TEST_APP_KEY = "123456"
  TEST_APP_SECRET = "654321"
  TEST_OAUTH_TOKEN = "QWERTY"
  TEST_OAUTH_TOKEN_SECRET ="YUIOP"
  TEST_OAUTH_VERIFIER = "ASDFG"
  TEST_ACCESS_TOKEN = "HJKLG"
  TEST_ACCESS_TOKEN_SECRET = "ZXCVB"
  protect_from_forgery :except => [:request_token,:authorize,:access_token]
  skip_before_filter :auth
  
  def request_token
    oauth_signature = params["oauth_signature"]
    puts "=======================params======================",params.inspect
    render :json=>%({"error":400,"detail":"need signature"}),:status => 400,:callback=>params[:callback] and return if oauth_signature.blank?
    oauth_signature = CGI::unescape oauth_signature
    oauth_params = {
        :oauth_consumer_key => params["oauth_consumer_key"],
        :oauth_timestamp => params["oauth_timestamp"],
        :oauth_nonce => params["oauth_nonce"],
        :oauth_version => params["oauth_version"] || "1.0",
        :oauth_signature_method => "HMAC-SHA1"
    }
    oauth_params[:oauth_callback] = params["oauth_callback"] if params["oauth_callback"]
    oauth_params[:oauth_body_hash] = params["oauth_body_hash"] if params["oauth_body_hash"]

    httpmethod = request.method.to_s
    base_uri = "http://#{request.headers["HTTP_HOST"]}/oauth/request_token"
    key = "#{TEST_APP_SECRET}&"
    create_base_string = OauthUtil.create_oauth_signature(httpmethod.upcase, base_uri, oauth_params, key)
    puts "===============oauth_signature,create_base_string",oauth_signature,create_base_string
    render :json=>%({"error":401,"detail":"Invalid signature"}),:status => 401,:callback=>params[:callback] and return if create_base_string != oauth_signature
    render :text => "oauth_token=#{TEST_OAUTH_TOKEN}&oauth_token_secret=#{TEST_OAUTH_TOKEN_SECRET}" if params["oauth_callback"].nil?
    render :text => "oauth_token=#{TEST_OAUTH_TOKEN}&oauth_token_secret=#{TEST_OAUTH_TOKEN_SECRET}&oauth_callback_confirmed=true" if params["oauth_callback"]
  end

  def authorize

    @token = params[:oauth_token]
    @oauth_callback = params[:oauth_callback]
    render :text => "no token error" and return  if @token.blank?
    
    if request.get?
       @name = "测试应用"
       render :action => "authorize"
       return
    end

    if request.post?
       username = params[:username]
       password = params[:password]
       if username.nil? || password.nil? || username != "test" || password !="test"
         flash[:notice] = "用户名或密码错误"
         render :action => "authorize"
         return
       else
         render :text => "授权已完成" and return if params[:oauth_callback].blank?
         callback = OauthUtil.callback_url(CGI::unescape(params[:oauth_callback]), @token, TEST_OAUTH_VERIFIER)
         redirect_to callback
         return
       end
    end
    return :text=>""
  end
  
  def access_token
    oauth_signature = params["oauth_signature"]
    render :json=>%({"error":400,"detail":"need signature"}),:status => 400,:callback=>params[:callback] and return if oauth_signature.blank?
    oauth_signature = CGI::unescape oauth_signature
    puts "=======================params======================",params.inspect

    ## for oauth
    oauth_params = {
        :oauth_consumer_key => params["oauth_consumer_key"],
        :oauth_token => params["oauth_token"],
        :oauth_timestamp => params["oauth_timestamp"],
        :oauth_nonce => params["oauth_nonce"],
        :oauth_version => params["oauth_version"] || "1.0",
        :oauth_signature_method => "HMAC-SHA1"
    }

    ## for xauth
    oauth_params = {
      	:x_auth_username => params["x_auth_username"],
      	:x_auth_password => params["x_auth_password"],
      	:x_auth_mode => "client_auth",
        :oauth_consumer_key => params["oauth_consumer_key"],
        :oauth_timestamp => params["oauth_timestamp"],
        :oauth_nonce => params["oauth_nonce"],
        :oauth_version => "1.0",
        :oauth_signature_method => "HMAC-SHA1"
    } if xauth?

    render :json=>%({"error":403,"detail":"unsupport XAuth"}),:status => 403,:callback=>params[:callback] and return if xauth?
    render :json=>%({"error":401,"detail":"Invalid signature"}),:status => 401,:callback=>params[:callback] and return if params["x_auth_username"] != "test" || params["x_auth_password"] != "test"

    oauth_params[:oauth_body_hash] = params["oauth_body_hash"] if params["oauth_body_hash"]
    oauth_params[:oauth_verifier] = params["oauth_verifier"] if params["oauth_verifier"]

    httpmethod = request.method.to_s
    base_uri = "http://#{request.headers["HTTP_HOST"]}/oauth/access_token"
    key = "#{TEST_APP_SECRET}&#{TEST_OAUTH_TOKEN_SECRET}"
    key = "#{TEST_APP_SECRET}&" if xauth? ## for xauth
    create_base_string = OauthUtil.create_oauth_signature(httpmethod.upcase, base_uri, oauth_params, key)
    puts "===============oauth_signature,create_base_string",oauth_signature,create_base_string
    render :json=>%({"error":401,"detail":"Invalid signature "}),:status => 401,:callback=>params[:callback] and return if create_base_string != oauth_signature
    render :text => "oauth_token=#{TEST_ACCESS_TOKEN}&oauth_token_secret=#{TEST_ACCESS_TOKEN_SECRET}"
  end

  def xauth?
    params["x_auth_username"] || params["x_auth_password"]
  end
end

 其中OauthUtil类源码如下

# coding: utf-8
require "uri"
class OauthUtil
  class << self
    ## 生成base_string 字符串
    def base_string(httpmethod,base_uri,request_params)
      base_str  = httpmethod + "&" + CGI::escape(base_uri) + "&"
      base_str += request_params.to_a.sort{|a,b| a[0].to_s <=> b[0].to_s}.map{|param| CGI::escape(param[0].to_s) + "%3D"+ CGI::escape(param[1].to_s)}.join("%26")
      puts "===========base_string===========",base_str
      base_str
    end

    ## see http://stackoverflow.com/questions/1959486/digest-hmac-is-part-of-ruby-standard-lib
    def digest(value,key)
      puts "===========digest_key===========",key  
      signature = Base64.encode64 OpenSSL::HMAC.digest("SHA1", key, value)
      puts "===========signature===========",signature.strip  
      signature.strip
    end

    def create_oauth_signature(httpmethod,base_uri,request_params,key)
      create_base_string =  base_string httpmethod,base_uri,request_params
      digest create_base_string,key
    end

    def callback_url(url,oauth_token,oauth_verifier)
      begin
      uri = URI::parse(url)
      rescue Exception
        return ""
      end
      query_string = uri.query
      query_string = "&#{query_string}" if query_string
      "#{uri.scheme}://#{uri.host}:#{uri.port}#{uri.path}?oauth_token=#{oauth_token}&oauth_verifier=#{oauth_verifier}#{query_string}"
    end
  end
end

 涉及到的erb页面authorize.html.erb页面代码如下

是否要授权<%=@name%>应用,使用你在本网站的部分功能?
<form action="/oauth/authorize" method="post">
  <input type="hidden" value="<%=@token%>" name="oauth_token"/>
  <input type="hidden" value="<%=@oauth_callback%>" name="oauth_callback"/>
  账号:<input type="text" name="username"></input><br />
  密码:<input type="password" name="password"></input><br />
  <input type="submit" value="授权"/>
</form>

如果不想授权,请关闭此页面。

<p><%=flash[:notice]%></p>

 

Oauth_base_controller 主要方法为auth,为子类提供验证,具体源码如下:

# coding: utf-8 
class Oauth_base_controller < ApplicationController
  
  before_filter :handle_headers_oauth_string
  before_filter :auth

  ## 进行验证
  def auth
    authenticate = request.headers["AUTHORIZATION"] || request.headers["HTTP_AUTHORIZATION"]
    if authenticate.blank?
      auth_oauth
    else
      authenticate_method = authenticate[0,5]
      authenticate_body = authenticate[5,authenticate.size - 5]
      case authenticate_method
      when "Basic"
        @name_pwd = Base64.decode64(authenticate_body.strip)
        puts "name_pwd",@name_pwd
        render :json=>%({"error":401,"detail":"authenticate format error"}),:status => "401",:callback=>params[:callback] and return false if @name_pwd.split(":").size != 2
        auth_basic
      else "OAuth"
        auth_oauth
      end
    end
  end

  ## 要移除的相关非oauth计算签名参数
  def except_other_params
    except_params "realm",params["realm"]
    except_params "oauth_signature",params["oauth_signature"]
    except_params "action",params["action"]
    except_params "controller",params["controller"]
  end

  ## 将非oauth计算签名所需参数移动到except_params中去
  def except_params(key,value)
    @except_params = {} if @except_params.nil?
    @except_params[key] = value
    params.delete key.to_s
    params.delete key.to_sym
  end

  private

  ## 进行 Basic 验证
  def auth_basic
    username = @name_pwd.split(":")[0]
    password = @name_pwd.split(":")[1]
    render :json=>%({"error":401,"detail":"authenticate fail"}),:status => "401",:callback=>params[:callback] and return false if username != "test" || password != "test"
  end

  ## 进行oauth 验证
  def auth_oauth
    params["oauth_version"] ||= "1.0"
    puts "=======================params======================",params.inspect
    oauth_signature = params["oauth_signature"]
    render :json=>%({"error":400,"detail":"need signature"}),:status => "400",:callback=>params[:callback] and return if oauth_signature.blank?
    oauth_signature = CGI::unescape oauth_signature
    
    except_other_params
    
    httpmethod = request.method.to_s
    base_uri = "http://#{request.headers["HTTP_HOST"]}#{request.path}"
    key = "#{app_secret_by_oauth_consumer_key params["oauth_consumer_key"]}&#{token_secret_by_access_token params["oauth_token"]}"
    create_base_string = OauthUtil.create_oauth_signature(httpmethod.upcase, base_uri, params, key)
    puts "===============oauth_signature,create_base_string",oauth_signature,create_base_string
    render :json=>%({"error":401,"detail":"Invalid signature"}),:status => "401",:callback=>params[:callback] and return if create_base_string != oauth_signature
  end

  ## 查找app_key 对应的secret
  def app_secret_by_oauth_consumer_key(app_key)
    "654321"
  end

  ## 查找access_token 对应的secret
  def token_secret_by_access_token(access_token)
    "ZXCVB"
  end

  ## 整理header里面的oauth 的参数到params里面去
  def handle_headers_oauth_string
    authenticate = request.headers["AUTHORIZATION"] || request.headers["HTTP_AUTHORIZATION"]
    return true if authenticate.blank?

    oauth_method = authenticate[0,5]

    return true if oauth_method == "Basic"
    render :json=>%({"error":401,"detail":"http_headers content error"}),:status => "401",:callback=>params[:callback] and return false if oauth_method != "OAuth"

    oauth_body = authenticate[5,authenticate.size - 5]
    oauth_body.split(",").each do |header_param|
      next if header_param.split("=").size != 2
      k = header_param.split("=")[0].strip
      v = header_param.split("=")[1].strip.gsub(/\"/,"")
      params[k] = v
    end
  end
end

 

使用方式为,继承Oauth_base_controller,然后子类中的所有方法则都要进行验证后才能访问,如:

# coding: utf-8
class OauthTestController < Oauth_base_controller

  def index
    puts "=======================",request.headers.inspect
    render :text => request.headers.inspect
  end

end

 

当访问这个index方法的时候,会进行oauth或basic验证,如果通过则返回客户端的请求头字符串,否则返回相应的验证失败代码

 

分享到:
评论
2 楼 酷的飞上天空 2013-01-13  
zhangyuxiu 写道
请教下:
1. 获取APP_KEY及APP_SECRET属于服务器端的设计范围么?
2. 上述实现的服务器端应该等同于新浪微博开放平台吧?那么测试用的APP,APP_SECRET是如何获得的啊?


回答一:
APP_KEY及APP_SECRET的发放和验证是分开的。发放端一般是一个特定的应用管理平台,可以让用户自己提交应用然后平台自动生成这两个,也可能是管理员手动建立。
回答而:
上述实现只是自己研究新浪oauth客户端验证时,兴趣上来想自己实现下服务器,只能说简易实现,实际情况肯定要复杂的多的。

另:现在新浪已经换成oauth2了。
1 楼 zhangyuxiu 2012-12-25  
请教下:
1. 获取APP_KEY及APP_SECRET属于服务器端的设计范围么?
2. 上述实现的服务器端应该等同于新浪微博开放平台吧?那么测试用的APP,APP_SECRET是如何获得的啊?

相关推荐

    新浪微博OAuth 验证

    新浪微博OAuth 验证

    OAuth2认证之新浪微博

    OAuth2认证资源 实现微博的

    使用WebView实现新浪微博Oauth2.0认证(android)

    RT,使用WebView实现新浪微博Oauth2.0认证,非截取html代码获取验证码 界面跳转无副作用

    Oauth 2.0 新浪微博 腾讯微博

    Oauth 2.0 新浪微博 腾讯微博 实现2个微博的授权与发布一条微博。

    使用Oauth2.0实现新浪微博客户端(C#)

    这是我使用Oauth2.0实现新浪微博客户端(C#),里面有我申请的AppKey, AppSecret, 公开给大家尽情使用

    新浪微博OAuth授权的Java实现.pdf

    新浪微博OAuth授权的Java实现.pdf

    OAuth2 新浪微博

    本例子自己学习最近刚刚接触新浪微博的demo 逻辑不是很好 仅供需要的人参考 WeiBo1.0 没有用线程访问网络 4.0以上不能使用 WeiBo2.0s1 添加了线程访问网络 4.0测试能够...微博是通过OAuth2验证的方式 而非是OAuth方式

    OAUTH1.0腾讯登录与新浪微博登录

    OAUTH1.0腾讯登录与新浪微博登录 里面包括全代码,

    手把手教你开发新浪微博(完整版)

    一个完整的新浪微博客户端android版OAuth认证 开发我的新浪微博客户端-大图浏览以及保存篇 开发我的新浪微博客户端-阅读微博功能篇 开发我的新浪微博客户端-阅读微博UI篇 开发我的新浪微博客户端-用户首页面功能篇 ...

    OAuth2.0新浪微博简单示例

    帮助新手学习OAuth的一个很好的示例 http://blog.csdn.net/koastal/article/details/50282509

    新浪微博OAuth2.0认证实现登陆

    实现新浪微博的OAuth2.0认证登陆,有自己找的资源,以及一些对自己帮助很大的网页,由于新浪微博不支持OAuth1.0了,所以把项目里的新浪微博登陆改为OAuth2.0了

    易语言新浪微博开源oauth版

    易语言新浪微博开源oauth版源码例程程序结合易语言扩展界面支持库和WEB客户2.1版,实现新浪微博的oauth认证登录,及浏览、发布新浪微博信息。 本易语言例程还使用到易语言多线程支持库,易语言应用接口支持库,超文本...

    android仿新浪微博

    最新的《Android应用开发实战》源码版本,基于Oauth2验证的功能。

    ios-仿新浪微博项目.zip

    4. OAuth2.0 授权 5. 归档存储数据 6. AFN工具类封装 7. 网络请求业务类封装 8. menu菜单 9. MVVM 创建首页cell 10.特殊字段的点击 10. FMDB存储数据 11. 发送微博界面 12.自定义表情键盘,并发送表情 13....

    新浪和腾讯微博Oauth认证Demo(php版)

    3./class/下,文件名中含有If的文件,定义的是接口.总计3个接口文件:各个网站的api访问路径...5.实例中2个网站(新浪微博和腾讯微博)的OAuth已经实现,并且通过验证。本例中的OAuth客户端来源于新浪微博OAuth认证SDK。

    易语言源码易语言新浪微博开源oauth版.rar

    易语言源码易语言新浪微博开源oauth版.rar 易语言源码易语言新浪微博开源oauth版.rar 易语言源码易语言新浪微博开源oauth版.rar 易语言源码易语言新浪微博开源oauth版.rar 易语言源码易语言新浪微博开源oauth版....

    Android平台新浪微博开发实战视频教程(10集)

    教程名称:Android平台新浪微博开发实战视频教程(10集)教程目录:【】Android项目_-6(上)【】Android项目_新浪微博-4(上)【】Android项目_新浪微博Android客户端开发ppt【】Android项目_新浪微博OAuth认证-4(下)...

    新浪微博OAuth2.0登录

    本文档使用C#语言开发,MVC框架调用新浪微博第三方登录OAuth2.0接口。 详细介绍相关开发步骤信息。

    新浪微博 OAuth 授权Sample

    最近开放平台非常火,各大互联网公司都纷纷推出各自的开放平台,随之流行的 OpenID, OAuth 则成为认证和授权的主要技术。本文将以登陆新浪微博获得授权取得用户数据作为示例简单研究 OAuth 的使用

    新浪微博OAuth授权

    新浪微博OAuth授权的Java实现,这篇文章中,我想用比较通俗的语言来解释OAuth协议。

Global site tag (gtag.js) - Google Analytics