前后花了近一个星期,中间就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