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

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://*.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验证,如果通过则返回客户端的请求头字符串,否则返回相应的验证失败代码