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

新浪微博oauth简易客户端ruby实现

阅读更多

前后花了近一个星期,中间就sha1的加密就纠结了几天。。。

还有一些莫名奇妙的问题,也都是自己的马虎,和对oauth认证的一知半解的原因。

 

废话不多说,代码如下

require "cgi"
require "uri"
require "net/http"
require "openssl"
require "base64"
module Oauth
  #注:暂不支持发布图片微博功能。
  #
  #使用方法介绍
  #首先获取用户的授权,如果已获取授权则直接看第三步
  #1.首先取得未授权token,使用get_request_token方法,然后跳转到新浪用户授权页面
  #  def request_token
  #    client = Oauth::Client.new({
  #        :app_key => "3224168886",
  #        :app_secret => "3739fb8b93b6c1737bd3052460a125e3"
  #    })
  #
  #    token = client.get_request_token
  #    session[:oauth_token_secret] = token[1]
  #    # puts "=======================oauth_token_secret",session[:oauth_token_secret]
  #    callback_url = "http://localhost:5000/oauth_test/access_token"
  #
  #    redirect_to client.authorize token[0],callback_url
  #  end
  #
  #2.用户授权以后,通过callback_url返回自己的接收页面,接收已授权的token和oauth_verifier。使用已授权的token和其他参数获取用户的access_token和token_secret。
  #这两个参数可以保存到数据库,以后就可以直接使用而不用再次走1和2的授权流程了,access_token和token_secret的有效期为直到用户登陆微博在设置里面取消授权为止
  #    client = Oauth::Client.new({
  #        :app_key => "3224168886",
  #        :app_secret => "3739fb8b93b6c1737bd3052460a125e3"
  #    })
  #    render :text => client.get_access_token(params[:oauth_token], session[:oauth_token_secret],params[:oauth_verifier]).inspect
  #
  #3.使用授权过的token访问新浪的api接口,例子如下
  #
  #    client = Oauth::Client.new({
  #        :app_key => "3224168886",
  #        :app_secret => "3739fb8b93b6c1737bd3052460a125e3",
  #        :oauth_token => "you's oauth_token  ",
  #        :oauth_token_secret => "you's oauth_token_secret",
  #        :debug => true
  #    })
  #
  #    client.send_request_get "http://api.t.sina.com.cn/direct_messages.json"  ## 获取当前用户最新私信列表
  #    client.send_request_post "http://api.t.sina.com.cn/statuses/update.json",{:status=>CGI::escape("今天很暖和!!!")} ## 发送一条微博信息
  #    client.send_upload_request_post "http://api.t.sina.com.cn/statuses/upload.json",{:status=>CGI::escape("vim功能大全")},{:pic=>File.new("d:\\vi.jpg","rb")}
  #    client.send_upload_request_post "http://api.t.sina.com.cn/account/update_profile_image.json",{},{:image=>File.new("d:\\013.jpg","rb")}
  #    注:File.new("d:\\vi.jpg","rb")的打开方式mode一定要是rb即二进制方式打开,否则无法读取完整的图片信息
  #
  class Client
    attr_accessor :request_params
    
    ## :app_key 新浪的app_key
    ## :app_secret 新浪的app_secret
    ## :oauth_token 用户授权的access_token
    ## :oauth_token_secret 用户授权的oauth_token_secret
    ## :debug 是否输出具体的oauth参数,供查看调试
    def initialize(params)
      @request_params = {
        :request_token_url => "http://api.t.sina.com.cn/oauth/request_token",
        :access_token_url => "http://api.t.sina.com.cn/oauth/access_token",
        :authorize_url => "http://api.t.sina.com.cn/oauth/authorize",
        :oauth_consumer_key => params[:app_key],
        :oauth_consumer_secret => params[:app_secret],
        :oauth_token => params[:oauth_token],
        :oauth_token_secret => params[:oauth_token_secret],
        :debug => params[:debug]
      }
    end
    
    ## 此方法生成要跳出的页面url
    ## token 未授权的request_token
    ## callback_url 接受已授权token的url
    def authorize(token,callback_url)
      "#{@request_params[:authorize_url]}?oauth_token=#{token}&oauth_callback=#{CGI::escape(callback_url)}"
    end
    
    ## 获取未授权的token
    ## 返回参数为 [oauth_token,oauth_token_secret]
    ## 建议把oauth_token_secret存储到session或cookie,后面获取access_token的时候会使用
    ## 如果验证失败,则抛出异常。异常信息为新浪返回的错误字符串
    def get_request_token
      
      params = {
        :oauth_consumer_key => "#{@request_params[:oauth_consumer_key]}",
        :oauth_timestamp => Time.new.to_i.to_s,
        :oauth_nonce => (Time.new.to_i + 100).to_s,
        :oauth_version => "1.0a",
        :oauth_signature_method => "HMAC-SHA1"
      }
      
      oauth_signature = digest base_string("GET",@request_params[:request_token_url],params)
      
      params_string = "#{query_string(params)}&oauth_signature=#{CGI::escape(oauth_signature)}"
      
      request_result = get_request_result(@request_params[:request_token_url],params_string)
      raise request_result if request_result.split("&").size != 2
      return [request_result.split("&")[0].split("=")[1],request_result.split("&")[1].split("=")[1]]
    end
    
    ## oauth_token 用后授权后返回的oauth_token
    ## oauth_token_secret 用后授权后返回的oauth_token_secret
    ## oauth_verifier 第一步中返回的oauth_verifier
    ## 返回为参数为[oauth_token,oauth_token_secret,user_id]
    ## 如果验证失败,则抛出异常。异常信息为新浪返回的错误字符串
    def get_access_token(oauth_token,oauth_token_secret,oauth_verifier)
      httpmethod = "GET"
      params = {
        :oauth_consumer_key => "#{@request_params[:oauth_consumer_key]}",
        :oauth_token => oauth_token,
        :oauth_timestamp => Time.new.to_i.to_s,
        :oauth_nonce => (Time.new.to_i + 100).to_s,
        :oauth_version => "1.0a",
        :oauth_signature_method => "HMAC-SHA1",
        :oauth_verifier => oauth_verifier
      }
      
      oauth_signature = digest base_string(httpmethod,@request_params[:access_token_url],params),@request_params[:oauth_consumer_secret]+"&"+oauth_token_secret
      params_string = "#{query_string(params)}&oauth_signature=#{CGI::escape(oauth_signature)}"
      request_result = get_request_result(@request_params[:access_token_url],params_string)
      result_array = request_result.split("&")
      raise request_result if result_array.size != 3
      return [result_array[0].split("=")[1],result_array[1].split("=")[1],result_array[2].split("=")[1]]
    end
    
    ## 发送GET请求,访问新浪api接口
    ## address 新浪api接口url,不含参数
    def send_request_get(address,data={})
      request_handle(address,data,"GET")
    end
    
    ## 发送POST请求,访问新浪api接口,不能上传文件,如果要上传文件请用send_upload_request_post方法
    ## address 新浪api接口url,不含参数
    ## data 一个hash对象,里面的值如果包含特殊字符如请使用CGI::escape方法进行编码。
    def send_request_post(address,data={})
      request_handle(address,data,"POST")
    end
    
    ## 发送POST请求,访问新浪api接口
    ## address 新浪api接口url,不含参数
    ## data 一个hash对象,里面的值如果包含特殊字符如请使用CGI::escape方法进行编码。
    ## upload_file 一个hash对象,里面的key对应的值为File 对象,即要上传的文件对象
    ## upload_file不能为空,否则会抛出upload_file is empty 异常
    def send_upload_request_post(address,data={},upload_file={})
      raise "upload_file is empty" if upload_file.nil? || upload_file.size == 0
      request_handle(address,data,"POST",upload_file)
    end
    
    private
    
    ## 用户访问新浪api接口
    ## address 新浪api接口url,不含参数
    ## data 一个hash对象,里面的值如果包含特殊字符如请使用CGI::escape方法进行编码。
    ## method 发出request请求的方式
    def request_handle(address,data,method="GET",upload_files={})
      
      params = {
        :oauth_consumer_key => "#{@request_params[:oauth_consumer_key]}",
        :oauth_nonce => (Time.new.to_i + 100).to_s,
        :oauth_signature_method => "HMAC-SHA1",
        :oauth_token => "#{@request_params[:oauth_token]}",
        :oauth_timestamp => Time.new.to_i.to_s,
        :oauth_version => "1.0"
      }
      
			if upload_files.size > 0
				escape_data = {}
				data.each do |k,v|
					escape_data[k.to_s] = CGI::escape(data[k])
				end
				base_string_params = params.merge(escape_data)   # data from send_upload_request_post
			else
				base_string_params = params.merge(data)   # data from send_request_post
			end
      
      
      oauth_signature = digest base_string(method,address,base_string_params),@request_params[:oauth_consumer_secret]+"&"+@request_params[:oauth_token_secret]
      
      params[:oauth_signature] = oauth_signature
      
      #生成http header 字符串
      header = {
        "Authorization" => "OAuth #{header_string(params)}"
      }
      
      uri = URI.parse(address)
      case method.to_s.upcase
      when "GET"
        req = Net::HTTP::Get.new(uri.path)
        req = Net::HTTP::Get.new(uri.path + "?#{query_string(data)}") if data.size != 0
      when "POST"
        req = Net::HTTP::Post.new(uri.path)
        set_form_data(data,req,false) if upload_files.size == 0
        set_upload_form_data(data, req, false, upload_files) if upload_files.size == 0
        
        puts "===========post_body===========",req.body  if @request_params[:debug] && upload_files.size == 0
      else
        raise "no support request method!"
      end
      
      req["Authorization"] = header["Authorization"]  #设置http header
      
      res = Net::HTTP.start(uri.host,uri.port)do |http|
        http.request(req)
      end
      puts "===========request_reslult_body===========",res.body  if @request_params[:debug]
      return res.body if res.code == "200"   #如果返回状态码不为200,则表示访问失败。抛出返回失败的字符串
      raise res.body
    end
    
    ## 设置POST 方法的数据
    ## params post的参数, 一个hash对象
    ## request 要发出的Net::HTTP::Post对象
    ## escape 是否对post的内容进行再转义
    def set_form_data(params,request,escape=true)
      params_string = ""
      params.each do |k,v|
        params_string << "#{k.to_s}=#{CGI::escape(v.to_s)}&" if escape
        params_string << "#{k.to_s}=#{v.to_s}&" unless escape
      end
      params_string.chomp!("&")
      request["content_type"] = "application/x-www-form-urlencoded"
      request.body = params_string
    end
    
    ## 设置上传文件的post body
    ## params 普通参数
    ## request httppost的request对象
    ## escape 是否对post的内容进行再转义
    ## upload_files 上传的文件
    def set_upload_form_data(params,request,escape=true,upload_files={})
      boundary = "----rubyoauth"
      params_string = ""
      params.each do |k,v|
        params_string << "--#{boundary}\r\n"
        params_string << "Content-Disposition: form-data; name=\"#{k.to_s}\"\r\n\r\n"
        params_string << "#{CGI::escape(v)}\r\n" if escape
        params_string << "#{v}\r\n" unless escape
      end
      
      upload_files.each do |k,v|
        params_string << "--#{boundary}\r\n"
        params_string << "Content-Disposition: form-data; name=\"#{k.to_s}\"; filename=\"#{File.basename(v.path)}\"\r\n"
        params_string << "Content-Type: #{get_file_content_type v}\r\n\r\n"
        params_string << "#{v.read}\r\n"
      end
      params_string << "--#{boundary}--\r\n"
      request["content-type"] = "multipart/form-data; boundary=#{ boundary }"
      request.body = params_string
    end
    
    ## 通过文件名后缀判断文件的类型,jpg为image/jpeg,png为image/x-png,gif为image/gif,其他的一律为application/octet-stream
    def get_file_content_type(file)
      case File.extname(file.path).downcase
      when ".jpg"
        "image/jpeg"
      when ".png"
        "image/x-png"
      when ".gif"
        "image/gif"
      else
        "application/octet-stream"
      end
    end
    
    ## 发出请求,只有获取用户验证的时候用到这个方法
    def get_request_result(request_token_url,params_string)
      url = URI.parse(request_token_url)
      res = Net::HTTP.start(url.host, url.port) {|http|
        http.get(url.path+"?#{params_string}")
      }
      puts "===========request_reslult_body===========",res.body  if @request_params[:debug]
      return res.body if res.code == "200"
      raise res.body
    end
    
    ## 使用HMAC-SHA1进行加密
    ## see http://stackoverflow.com/questions/1959486/digest-hmac-is-part-of-ruby-standard-lib
    def digest(value,key="#{@request_params[:oauth_consumer_secret]}&")
      puts "===========digest_key===========",key  if @request_params[:debug]
      signature = Base64.encode64 OpenSSL::HMAC.digest("SHA1", key, value)
      puts "===========signature===========",signature.strip  if @request_params[:debug]
      signature.strip
    end
    
    ## 生成http header字符串
    def header_string(header_params)
      result_string = ""
      header_params.to_a.sort{|a,b| a[0].to_s <=> b[0].to_s}.each do|param|
        result_string << "#{param[0]}=" << '"' << "#{CGI::escape(param[1])}"<< '",'
      end
      puts "===========header_string===========",result_string.chomp(",")  if @request_params[:debug]
      result_string.chomp(",")
    end
    
    ## 生成query_string 字符串
    def query_string(query_params)
      result_string = ""
      query_params.to_a.sort{|a,b| a[0].to_s <=> b[0].to_s}.each do|param|
        result_string << "#{param[0]}=#{CGI::escape(param[1])}&"
      end
      puts "===========query_string===========",result_string.chomp("&")  if @request_params[:debug]
      result_string.chomp("&")
    end
    
    ## 生成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  if @request_params[:debug]
      base_str
    end
    
  end
end
 

在做图片上传的时候遇到图片只能读取一部分的问题,后来发现是打开方式不对,

把File.new("d:\\vi.jpg") 改为File.new("d:\\vi.jpg","rb") 就可以读取完整的图片数据了。。。 这个问题纠结了几个小时,郁闷。

 

同时发布到新浪微博论坛

http://forum.open.t.sina.com.cn/read.php?tid=558

分享到:
评论
4 楼 酷的飞上天空 2011-08-12  
leiyulyl 写道
我client.send_upload_request_post "http://api.t.sina.com.cn/statuses/upload.json",{:status=>CGI::escape("vim功能大全")},{:pic=>File.new("d:\\vi.jpg","rb")}  这样发微博怎么发不出啊 ...也没报错


你应该是用的早期验证获得的oauth_token和oauth_token_secret  重新验证下获取新的再测试下应该就没问题了。

3 楼 leiyulyl 2011-07-26  
我client.send_upload_request_post "http://api.t.sina.com.cn/statuses/upload.json",{:status=>CGI::escape("vim功能大全")},{:pic=>File.new("d:\\vi.jpg","rb")}  这样发微博怎么发不出啊 ...也没报错
2 楼 404714 2011-07-19  
似乎不行, 我错了。
1 楼 404714 2011-07-19  
base_string方法写的太纠结
base_str = "POST&" + CGI::escape("http://api.t.sina.com.cn/oauth/access_token") + "&" + request_params.map{|k,v| "#{k}%3D#{URI.escape(v.to_s, /[^a-zA-Z0-9\-\.\_\~]/)}"}.sort * "%26"

或者
base_str = "POST&" + CGI::escape("http://api.t.sina.com.cn/oauth/access_token") + "&" + request_params.map{|k,v| "#{k}%3D#{CGI::escape(v.to_s)}"}.sort * "%26"

相关推荐

Global site tag (gtag.js) - Google Analytics