酷的飞上天空 发表于 2013-1-29 22:17:28

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 nonceclass OauthController < Oauth_base_controllerTEST_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"]enddef authorize    @token = params[:oauth_token]    @oauth_callback = params[:oauth_callback]    render :text => "no token error" and returnif @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}"enddef xauth?    params["x_auth_username"] || params["x_auth_password"]endend 其中OauthUtil类源码如下
# coding: utf-8require "uri"class OauthUtilclass << 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.to_s <=> b.to_s}.map{|param| CGI::escape(param.to_s) + "%3D"+ CGI::escape(param.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}"    endendend 涉及到的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_stringbefore_filter :auth## 进行验证def auth    authenticate = request.headers["AUTHORIZATION"] || request.headers["HTTP_AUTHORIZATION"]    if authenticate.blank?      auth_oauth    else      authenticate_method = authenticate      authenticate_body = authenticate      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    endend## 要移除的相关非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 = value    params.delete key.to_s    params.delete key.to_symendprivate## 进行 Basic 验证def auth_basic    username = @name_pwd.split(":")    password = @name_pwd.split(":")    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_signatureend## 查找app_key 对应的secretdef app_secret_by_oauth_consumer_key(app_key)    "654321"end## 查找access_token 对应的secretdef 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    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    oauth_body.split(",").each do |header_param|      next if header_param.split("=").size != 2      k = header_param.split("=").strip      v = header_param.split("=").strip.gsub(/\"/,"")      params = v    endendend 
使用方式为,继承Oauth_base_controller,然后子类中的所有方法则都要进行验证后才能访问,如:
# coding: utf-8class OauthTestController < Oauth_base_controllerdef index    puts "=======================",request.headers.inspect    render :text => request.headers.inspectendend 
当访问这个index方法的时候,会进行oauth或basic验证,如果通过则返回客户端的请求头字符串,否则返回相应的验证失败代码
 
页: [1]
查看完整版本: Oauth简易服务器端ruby实现,仿新浪微博验证的方式