RubyでNet::HTTPを使ってゴリゴリとTwitterのOAuth認証してみた
注:
最初OAuthライブラリの使い方を調べないままNet::HTTPを使って試してみたものです。
OAuthライブラリ使うだけでかなり手軽に書けます、ほんとに。
TwitterでのOAuth認証
OAuth認証の流れとしては
- アプリを登録しconsumer_key, consumer_secretを取得
- この2つを使い、リクエストトークン(oauth_token, oauth_token_secret)を取得
- ここまでに出てきた4つとPINコードを使い、アクセストークンを取得
- あとはTwitterライブラリを使うなり、自分でゴリゴリ書く
コード
https://gist.github.com/1385914
require 'openssl' require 'uri' require 'net/http' require 'rubygems' require 'twitter' class OAuthAccessToken def initialize(consumer_key, consumer_secret) @request_token_uri = 'http://twitter.com/oauth/request_token' @access_token_uri = 'http://twitter.com/oauth/access_token' @authorize_uri = 'http://api.twitter.com/oauth/authorize' @consumer_secret = consumer_secret @token = '' @token_secret = '' # oauthのヘッダ @header = { 'oauth_consumer_key' => consumer_key, 'oauth_nonce' => 'ABCDEFG', 'oauth_signature_method' => 'HMAC-SHA1', 'oauth_timestamp' => Time.now.to_i.to_s, 'oauth_version' => '1.0' } create_signature(@request_token_uri) get_request_token get_pincode unless @token == '' end def get_access_token uri = URI.parse(@access_token_uri) http = Net::HTTP.new(uri.host) http.start do |h| create_signature(@access_token_uri, @token_secret) response = h.get("#{uri.path}?#{concat_header}") if response.code == '200' parse_response(response.body) else puts response.code puts response.body puts "error" end end end private def escape (value) URI.escape(value, /[^a-zA-Z0-9_\-\.]/) end # ヘッダの要素を連結 def concat_header params = '' @header.each do |key,value| params += "#{key}=#{value}&" end params.chop end # シグニチャを作成 def create_signature(token_uri, access_token_secret = '') # ヘッダのパラメータを連結して、HTTPメソッドとも連結 params = concat_header value = "GET&#{escape(token_uri)}&#{escape(params)}" # signature keyの作成 signature_key = "#{@consumer_secret}&#{access_token_secret}" # hmac_sha1 sha1 = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, signature_key, value) @header['oauth_signature'] = [sha1].pack('m').gsub(/\n/, '') end def get_request_token uri = URI.parse(@request_token_uri) http = Net::HTTP.new(uri.host) http.start do |h| response = h.get("#{uri.path}?#{concat_header}") if response.code == '200' tokens = parse_response(response.body) @token = tokens['oauth_token'] @token_secret = tokens['oauth_token_secret'] @header['oauth_token'] = @token else p response.code end end end def get_pincode authorize_uri = "#{@authorize_uri}?oauth_token=#{@token}" system('open', authorize_uri) || puts("Access here: #{authorize_uri}\nand...") print 'input PIN Code: ' @header['oauth_verifier'] = $stdin.gets.strip.chomp end # レスポンスのボディをパース def parse_response(body) params = { } body.split('&').each do |param| token = param.split('=') params[token[0]] = token[1] end params end end if ARGV[0] && ARGV[1] consumer_key = ARGV[0] consumer_secret = ARGV[1] else consumer_key = $stdin.gets.chomp consumer_secret = $stdin.gets.chomp end tokens = OAuthAccessToken.new(consumer_key, consumer_secret).get_access_token if tokens.class == Hash Twitter.configure do |config| config.consumer_key = consumer_key config.consumer_secret = consumer_secret config.oauth_token = access_token config.oauth_token_secret = access_token_secret end Twitter.home_timeline.first.text else puts "Error: cannot get 'access_token' and 'access_token_secret'" end
コードについての忘備録
シグニチャ生成について
oauth_consumer_key | Twitterで提供されるconsumer_key |
oauth_nonce | 一意な値 |
oauth_signature | 認証のための暗号 |
oauth_signature_method | 認証方式 |
oauth_timestamp | タイムスタンプ(ミリ秒) |
oauth_version | バージョン(必須ではないが、付ける場合は"1.0") |
oauth_token | リクエストトークン |
oauth_verifier | PIN コード |
これらはURLの後ろにつけるパラメータ
- リクエストトークンを得る際にはoauth_token, oauth_verifierは要らない(というかまだ持ってない)
- アクセストークンを得る際には全て使う
HTTPメソッド+URL+パラメータを連結した文字列を署名キーをもとに暗号化して、base64方式で暗号化
署名キーは以下の文字列
-
- リクエストトークンではconsumer_secretのみ
- アクセストークンではconsumer_secretとoauth_token_secretを&で連結したもの
引っかかったところ
- accessをacessとtypo
- 変数名はいいが、URLで間違えて302のレスポンスコードばかり
- アクセストークンを得る際にURLをリクエストトークンのものにしていた
- oauth_tokenをaccess_tokenと書いて早とちり
typoがひどいコードだった