OAuthã³ã³ã·ã¥ã¼ãã®ä»çµã¿ã¨å®è£ ã Rubyç·¨
åç½®ã
前回の記事ã§OAuthã使ã£ã¦Twitter APIã«ã¢ã¯ã»ã¹ãããã¨ãã§ããããã«ãªãã¾ããããruby-oauthã¯å é¨ã§Net::HTTPãå¼ã³åºãã¦ããããããã®ã¾ã¾ã§ã¯Google App Engine for Javaä¸ã®JRuby(以ä¸JRuby for GAE/J)ã§å©ç¨ã§ãã¾ããã
ãJRuby for GAE/Jã§ãNet::HTTPã使ããããã«ãªããã¨ããrb-gae-supportã¨çµã¿åãããã°OKãªã®ããããã¾ããã*1ãOAuthã®ä»æ§èªä½ã¯ã·ã³ãã«ãªãã®ã§ããããã£ãããªã®ã§åå¼·ãã¦ãèªåã§å®è£ ãã¦ã¿ããã¨ã«ãã¾ãã
è»è¼ªã®åçºæããããã§ãï¼*2
ã¡ãªã¿ã«ã¿ã¤ãã«ã«Rubyç·¨ã¨ä»ãã¦ãã¾ãããä»ã®è¨èªç·¨ãä½æããäºå®ã¯ç¹ã«ããã¾ããã
OAuthã®ä»æ§
å®è£ ã®åã«OAuthã®ä»æ§ããããããã®æãç«ã¡ã«ã¤ãã¦èª¿ã¹ã¾ãããæ¢ã«ããããããã¾ã¨ãè¨äºãããã¤ãæ¸ããã¦ãã¾ãã®ã§ãããã§ã¯ããããè²¼ãä»ããã ãã«ã¨ã©ãã¦ããã¾ãã
OAuthã®æãç«ã¡ãæ¦è¦ãç解ããã®ãªããã¡ãã
ãèªè¨¼ãã¨ãèªå¯ãã®éããOpenIDã¨ã®æ¯è¼ãªã©ã«ã¤ãã¦ã¯ãã¡ãã
- OpenID や OAuth の役割と、既存のシングル・サインオンとの違い:Goodpic
- WebAPI のアクセス制御に使える OAuth という仕様 - まちゅダイアリー(2007-09-25)
- 仕様から学ぶOpenIDのキホン − @IT
ãªãã£ã·ã£ã«ãªè³æã«ãç®ãéãã¦ããã¾ãã
2009å¹´8æç¾å¨ãOAuthã®ä»æ§ã¯"1.0"ã¨"1.0 Revision A"ãããã¾ããããã®è¨äºã§ã¯ããæ°ãã"1.0 Revision A"ãå ã«å®è£ ãã¦ãã¾ãã
å®è£ ããç¯å²
ã·ã³ãã«ãªä»æ§ã¨ã¯è¨ããä»æ§ã«è¼ã£ã¦ãããã®ãå ¨ã¦å®è£ ãããã¨ããã¨ãããªãã«å¤§å¤ã§ãã*3
ã§ãã®ã§ãããã¾ã§ãæçµçãªç®æ¨ã§ãããJRuby for GAE/Jã§åä½ããTwitterã®botããä½ãã®ã«æä½éå¿ è¦ãªæ©è½ã ãã«çµã£ã¦å®è£ ãã¦ã¿ããã¨æãã¾ãã
ã¾ããã¯ã©ã¤ã¢ã³ããä½ãããã§ããããå½ç¶âã³ã³ã·ã¥ã¼ãâãå®è£ ãããã¨ã«ãªãã¾ããã
ã¾ãã前回の記事ã§ã触ããããã«ãç¾æç¹ã§ã¯Twitterã®ã¢ã¯ã»ã¹ãã¼ã¯ã³ã«æéåãã¯ããã¾ããã®ã§ãããã¯æåã§é å¼µããã¨ã«ããã¨ãã¢ã¯ã»ã¹ãã¼ã¯ã³ãåå¾ããã¾ã§ã®å¦çã®å®è£ ã¯å¿ è¦ããã¾ããã
æ´ã«ãTwitterã¯HMAC-SHA1å½¢å¼ã®ç½²åãããµãã¼ããã¦ãã¾ããã®ã§ããã以å¤ã®ç½²åãå®è£ ããå¿ è¦ãããã¾ããã
OAuthãããã³ã«ãã©ã¡ã¼ã¿ã®æ¸¡ãæ¹ãAuthorizationãããã使ã£ãæ¹æ³ã ãç¨æãã¦ããã°ååã§ãããã
ãã®ããã«åãè©°ãã¦ããã¨ãæçµçã«ã¯
- æ¢ã«åå¾æ¸ã¿ã®ã¢ã¯ã»ã¹ãã¼ã¯ã³ã使ã
- HMAC-SHA1ã§ç½²åãããOAuthãããã³ã«ãã©ã¡ã¼ã¿ã
- Authorizationãããã«æ ¼ç´ã
- ãµã¼ãã¹ãããã¤ãããä¿è·ããããªã½ã¼ã¹ãåå¾ãã
ã¨ããä¸é£ã®æé ãå®è¡ã§ããã ãã®æ©è½ãããã°OKãã¨ãããã¨ã«ãªãã¾ããããªãã·ã³ãã«ãªã©ã¤ãã©ãªã«ã§ãããã§ãã
å®è£ ã®åæ
æçµçã«ã¯JRuby for GAE/Jã§åããããã®ã§ãããè²ã 試è¡é¯èª¤ãã¦ä½ã£ã¦ããã®ãªãã°ããã¯ãç´ ã®Rubyã®ã»ããä½ãã¨ãæ軽ã§ãã
ã¨ããããã§ãã¾ãã¯æ®éã«Net::HTTPã使ã£ã¦éä¿¡ãããããªã©ã¤ãã©ãªãæ¸ããã¨ã«ãã¾ãããããã§ããã°ããã¨ã¯Net::HTTPé¨åãGAEã®URLFetchã«ç½®ãæããã®ã¯ããã»ã©é£ããä½æ¥ã§ã¯ãªãããã§ãã
ä¸å¿ã1.8.7ã¨1.9.1両æ¹ã§åãããã«ã¯ãã¦ããã¤ããã§ãã
æ³å®ãã使ãæ¹
OAuthã«ããèªè¨¼ã«å¿ è¦ãªã³ã³ã·ã¥ã¼ããã¼ãã³ã³ã·ã¥ã¼ãç§å¯éµãã¢ã¯ã»ã¹ãã¼ã¯ã³ãã¢ã¯ã»ã¹ãã¼ã¯ã³ç§å¯éµãå¼æ°ã«ä¸ãã¦ã¤ã³ã¹ã¿ã³ã¹ãçæããNet::HTTPã®ããã«getãpostãªã©ã®HTTPã¡ã½ããåã§ãã®ã¾ã¾HTTPéä¿¡ãå®è¡ã§ãããããªå½¢ã«ãã¾ãã
ãããªæãã®ã³ã¼ãã«ãªãããã§ãã
simple_oauth = SimpleOAuth.new(CONSUMER_KEY, CONSUMER_SECRET, TOKEN, TOKEN_SECRET) response = simple_oauth.get('http://twitter.com/statuses/friends_timeline.json') response = simple_oauth.post('http://twitter.com/statuses/update.json', { :status => 'This is a test tweet.' })
åã¡ã½ããã®æ»ãå¤ã¯Net::HTTPã¨åããNet::HTTPResponseã®ã¤ã³ã¹ã¿ã³ã¹ã¨ãã¾ãã
å®è£
ã§ã¯ã仕様ã¨ã«ããã£ããã¤ã¤ãå°ããã¤å®è£ ãé²ãã¦ãã£ã¦ã¿ã¾ãã
ã¤ã³ã¿ã¼ãã§ã¼ã¹
ã¤ã³ã¿ã¼ãã§ã¼ã¹é¨åã®å®è£ ã¯ãããªæãã«ãªãã¾ãã
class SimpleOAuth def initialize(consumer_key, consumer_secret, token, token_secret) @consumer_key = consumer_key @consumer_secret = consumer_secret @token = token @token_secret = token_secret # This class supports only 'HMAC-SHA1' as signature method at present. @signature_method = 'HMAC-SHA1' end def get(url, headers = {}) request(:GET, url, nil, headers) end def head(url, headers = {}) request(:HEAD, url, nil, headers) end def post(url, body = nil, headers = {}) request(:POST, url, body, headers) end def put(url, body = nil, headers = {}) request(:PUT, url, body, headers) end def delete(url, headers = {}) request(:DELETE, url, nil, headers) end end
ç¹ã«è¨åãã¹ãé¨åã¯ããã¾ããã
HTTPãªã¯ã¨ã¹ãã®å®è¡
getãpostãªã©ã®ã¡ã½ããããå¼ã³åºãããrequestã¡ã½ããã®å®è£ ã§ãã
def request(method, url, body = nil, headers = {}) method = method.to_s url = URI.parse(url) request = create_http_request(method, url.request_uri, body, headers) request['Authorization'] = auth_header(method, url, request.body) Net::HTTP.new(url.host, url.port).request(request) end
HTTPãªã¯ã¨ã¹ããªãã¸ã§ã¯ãã®ä½æã¯å°ã æããããã®ã§å¥ã¡ã½ããã«ãã¦ãã¾ãã
auth_headerã¡ã½ããã§OAuthç¨ã®èªè¨¼ããããçæããAuthorizationãããã«ã»ãããã¦ãã¾ãã
ãã¨ã¯ãã®ã¾ã¾HTTPãªã¯ã¨ã¹ããå®è¡ãã¦ããã ãã§ãã
æ±ç¨ã¡ã½ãã
次ã®ã¡ã½ããã®å®è£ ã«ç§»ãåã«ããã以éãã©ã¡ã¼ã¿ã®ã¨ã³ã³ã¼ããé£çµãªã©ã®å¦çãé »ç¹ã«å¿ è¦ã«ãªã£ã¦ãã¾ãã®ã§ãå ±éã«ä½¿ãããã«æ±ç¨ã¡ã½ããã¨ãã¦å®è£ ãã¦ããã¾ãã
ã¾ããã©ã¡ã¼ã¿ã®ã¨ã³ã³ã¼ããããé¢é£ããä»æ§ã¯5.1. Parameter Encodingã§ãã
RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~]/ def escape(value) URI.escape(value.to_s, RESERVED_CHARACTERS) end
RESERVED_CHARACTERSã¯ä»æ§ã«æè¨ããã¦ãããã®ããã®ã¾ã¾æã£ã¦ããã ãã§ãã
次ã«ãã©ã¡ã¼ã¿ã®é£çµãããã®å¦çã¯åãã©ã¡ã¼ã¿ã®ã¨ã¹ã±ã¼ããå«ã¿ã¾ãã
def encode_parameters(params, delimiter = '&', quote = nil) if params.is_a?(Hash) params = params.map do |key, value| "#{escape(key)}=#{quote}#{escape(value)}#{quote}" end else params = params.map { |value| escape(value) } end delimiter ? params.join(delimiter) : params end
å ´æã«ãã£ã¦å¤ãå¼ç¨ç¬¦ã§å²ãå¿ è¦ããã£ãããé£çµããéã®åºåãæåãç°ãªã£ããããã®ã§ãå ¨ã¦ã«å¯¾å¿ã§ããããã«æè»ãªå®è£ ã«ãã¦ãã¾ãã
HTTPãªã¯ã¨ã¹ããªãã¸ã§ã¯ãã®ä½æ
ã§ã¯ãå ç¨ã®requestã¡ã½ããããå¼ã³åºããã¦ããã¡ã½ããã®å®è£ ã«æ»ãã¾ãã
VERSION = '0.1' USER_AGENT = "SimpleOAuth/#{VERSION}" def create_http_request(method, path, body, headers) method = method.capitalize.to_sym request = Net::HTTP.const_get(method).new(path, headers) request['User-Agent'] = USER_AGENT if method == :Post || method == :Put request.body = body.is_a?(Hash) ? encode_parameters(body) : body.to_s request.content_type = 'application/x-www-form-urlencoded' request.content_length = (request.body || '').length end request end
å¼æ°ã§åãåã£ãã¡ã½ããåã«å¯¾å¿ããã¯ã©ã¹ãconst_getã§åå¾ããã¤ã³ã¹ã¿ã³ã¹ãçæãã¾ãã
ãªã¯ã¨ã¹ãããã£ã®å½¢å¼ã¯"application/x-www-form-urlencoded"ããæ³å®ãã¦ãã¾ãããTwitter APIã§ä½¿ããªãä»ã¯ç¹ã«å¿ è¦ãªãããªãã¨æãã¾ãã®ã§â¦ã
ãã£ãããªã®ã§User-Agentãå ¥ããããã«ãã¾ããã
OAuthèªè¨¼ç¨ãããã®çæ
ããä¸ã¤ãrequestã¡ã½ããããå¼ã³åºããã¦ããã¡ã½ããã®å®è£ ã§ããAuthorizationãããã«ã»ããããèªè¨¼ç¨ãã¼ã¿ãçæãã¾ããé¢é£ããä»æ§ã¯5.4. OAuth HTTP Authorization Schemeã§ãã
def auth_header(method, url, body) parameters = oauth_parameters parameters[:oauth_signature] = signature(method, url, body, parameters) 'OAuth ' + encode_parameters(parameters, ', ', '"') end
oauth_parametersã¡ã½ããã§OAuthãããã³ã«ãã©ã¡ã¼ã¿ãåå¾ããããã«ã¡ã½ããåãURLããªã¯ã¨ã¹ãããã£ãªã©ã®æ å ±ãæ··ãã¦ãsignatureã¡ã½ããã§âç½²åâãçæãã¾ããç½²åãoauth_signatureãã©ã¡ã¼ã¿ã«ã»ããããããã¾ã¨ãã¦ã¨ã³ã³ã¼ããã¦OAuthèªè¨¼ç¨ãããã®å®æã§ãã
OAuthãããã³ã«ãã©ã¡ã¼ã¿ã®ç¨æ
ã§ã¯ããã®oauth_parametersã¡ã½ããã®å®è£ ã§ããOAuthèªè¨¼ã«å¿ è¦ãªä¸é£ã®ãã©ã¡ã¼ã¿ãç¨æãã¾ããé¢é£ããä»æ§ã¯7. Accessing Protected Resourcesã§ãã
OAUTH_VERSION = '1.0' def oauth_parameters { :oauth_consumer_key => @consumer_key, :oauth_token => @token, :oauth_signature_method => @signature_method, :oauth_timestamp => timestamp, :oauth_nonce => nonce, :oauth_version => OAUTH_VERSION } end
å¿ è¦ãªãã©ã¡ã¼ã¿ã¯ã³ã³ã·ã¥ã¼ããã¼ãã¢ã¯ã»ã¹ãã¼ã¯ã³ãç½²åã®å½¢å¼ãã¿ã¤ã ã¹ã¿ã³ããã©ã³ãã ãªä¸æã®æååãããã¦OAuthã®ãã¼ã¸ã§ã³ã§ãã
åè 3ã¤ã¯æ¢ã«ã¤ã³ã¹ã¿ã³ã¹å¤æ°ã¨ãã¦ã»ããæ¸ã¿ã§ããtimestampã¨nonceã¡ã½ããã¯æ¬¡ã«ãOAuthã®ãã¼ã¸ã§ã³ã¯ç¾ç¶'1.0'åºå®ã§ãã
ã¿ã¤ã ã¹ã¿ã³ãã¨Nonce
timestampã¨nonceã¡ã½ããã®å®è£ ã§ããé¢é£ããä»æ§ã¯8. Nonce and Timestampã§ãã
def timestamp Time.now.to_i.to_s end def nonce OpenSSL::Digest::Digest.hexdigest('MD5', "#{Time.now.to_f}#{rand}") end
ã¿ã¤ã ã¹ã¿ã³ãã¯ãã®ã¾ã¾ãç¾å¨æå»ã®åºæºæããã®çµéç§ã§ãã
Nonceã¯ã©ã³ãã ãªä¸æã®æååã§ããã°ãä»ã«ç´°ããæå®ã¯ããã¾ããããªã¯ã¨ã¹ãã®ã¦ãã¼ã¯æ§ãç¶æããããã ãã®å¤ã§ããããã§ã¯ã¨ããããç¾å¨æå»ã«ã©ã³ãã æ°å¤ãæ··ãããã®ã®MD5ããã·ã¥å¤ã¨ãã¦ãã¾ãã
MD5ããã·ã¥å¤ã®çæã«Digest::MD5ã§ã¯ãªãOpenSSLã¢ã¸ã¥ã¼ã«ãå©ç¨ãã¦ããã®ã¯ãã©ã¡ãã«ããå¾ã§OpenSSLã¢ã¸ã¥ã¼ã«ãå¿ è¦ã«ãªãããã§ãã
def nonce Digest::MD5.hexdigest("#{Time.now.to_f}#{rand}") end
ã§ãåãã§ãã
ç½²å
ç½²åã¡ã½ããã®å®è£ ã«ç§»ãã¾ããé¢é£ããä»æ§ã¯9. Signing Requestsã§ãã
def signature(*args) base64(digest_hmac_sha1(signature_base_string(*args))) end
åè¿°ã®ããã«ãä»åã¯HMAC-SHA1ã«ããç½²åãããµãã¼ããã¾ããã®ã§ãå®è£ ãHMAC-SHA1éå®ã¨ãªã£ã¦ãã¾ãã
ç½²åã®æé ã¨ãã¦ã¯ã
- åãåã£ããã©ã¡ã¼ã¿(ã¡ã½ããåãURLããªã¯ã¨ã¹ãããã£ãOAuthãããã³ã«ãã©ã¡ã¼ã¿)ããSignature Base String(ç½²ååºæºæåå)ãä½æããã(signature_base_stringã¡ã½ãã)
- Signature Base Stringã¨ç§å¯éµãçµã¿åããã¦HMAC-SHA1ã®ãã¤ã¸ã§ã¹ãå¤ãçæããã(digest_hmac_sha1ã¡ã½ãã)
- ãã¤ã¸ã§ã¹ãå¤ãBase64ã§ã¨ã³ã³ã¼ãããã(base64ã¡ã½ãã)
ã¨ãªãã¾ãã
HMAC-SHA1ãã¤ã¸ã§ã¹ãã¨Base64ã¨ã³ã³ã¼ã
ãã®äºã¤ã¯é常ã«ã·ã³ãã«ã§ãã®ã§ãå ã«å®è£ ãæ¸ã¾ãã¦ãã¾ãã¾ããé¢é£ããä»æ§ã¯9.2. HMAC-SHA1ã§ãã
def base64(value) [ value ].pack('m').gsub(/\n/, '') end def digest_hmac_sha1(value) OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, secret, value) end def secret escape(@consumer_secret) + '&' + escape(@token_secret) end
base64ã¡ã½ããã¯åã«Base64ã¨ã³ã³ã¼ãã£ã³ã°ãæ½ãã¦ããã ãã§ããå¤ä¸ã®æ¹è¡ã¯ä¸è¦ãªã®ã§å ¨é¨åã£ã¦ãã¾ãã
HMACã®ä»çµã¿ã¯ä»¥ä¸ã®ãã¼ã¸ãåèã«ãªãã¾ããããã§ã¯OpenSSLã¢ã¸ã¥ã¼ã«ã®åãåãã¦ãµã¯ã£ã¨ã
secretã¡ã½ããã¯ç§å¯éµã®çæã§ããç§å¯éµã¯ã³ã³ã·ã¥ã¼ãç§å¯éµã¨ã¢ã¯ã»ã¹ãã¼ã¯ã³ç§å¯éµãããããã¨ã¹ã±ã¼ããã¦&è¨å·ã§é£çµãããã®ã«ãªãã¾ãã
Signature Base Stringã®ä½æ
ã§ã¯signature_base_stringã¡ã½ããã®å®è£ ã«ç§»ãã¾ããé¢é£ããä»æ§ã¯9.1. Signature Base Stringã§ãã
def signature_base_string(method, url, body, parameters) method = method.upcase base_url = signature_base_url(url) parameters = normalize_parameters(parameters, body, url.query) encode_parameters([ method, base_url, parameters ]) end
ã¡ã½ããåã¯å¤§æåã«ãã¾ãã
URLã¯æ¬¡ã®ãããªå¦çã§å¿ è¦ãªé¨åã ãã«åãè½ã¨ãã¾ãã
def signature_base_url(url) URI::HTTP.new(url.scheme, url.userinfo, url.host, nil, nil, url.path, nil, nil, nil) end
OAuthãããã³ã«ãã©ã¡ã¼ã¿ããªã¯ã¨ã¹ãããã£ãURLä¸ã®ã¯ã¨ãªãã¼ã¿ã¯æ¬¡ã®ãããªå¦çã§âæ£è¦åâãæ½ãã¾ãã
def normalize_parameters(parameters, body, query) parameters = encode_parameters(parameters, nil) parameters += body.split('&') if body parameters += query.split('&') if query parameters.sort.join('&') end
ãããã®ãã¼ã¿ã«å«ã¾ããâåå=å¤âã®ãã¢(ããããã¨ã¹ã±ã¼ãæ¸ã¿)ããã¡ãæ··ãã«ãã¦ã½ã¼ãããã²ã¨ããã¾ãã«é£çµãã¾ãã
以ä¸ã®ããã«ãã¦å¾ããã大æåã¡ã½ããåã»åºæ¬URLã»æ£è¦åãã©ã¡ã¼ã¿ãã¨ã¹ã±ã¼ãï¼é£çµããã°Signature Base Stringã®å®æã§ãã
å®æï¼
ä¸ããä¸ã¸å®è£
ãã¦ãã£ãã®ã§ãã©ãã¾ã§å®è£
ãçµãã£ã¦ããã®ãããããã«ããã£ãããããã¾ããããããã§å
¨ã¦ã®å®è£
ãå®äºã§ããããã¾ã§ã®ã³ã¼ããã³ãã¼ãã¦ã¾ã¨ããã ãã§åãããã«ãªãã¾ãã*4
使ãæ¹ã®ãµã³ãã«
ã§ã¯å®éã«ãä»ä½ã£ãã©ã¤ãã©ãªâ¦simple-oauth.rbã¨ããååã§ä¿åããã¨ãã¾ããã使ã£ã¦Twitterã®APIã¸ã¢ã¯ã»ã¹ãããµã³ãã«ã³ã¼ããæ¸ãã¦ã¿ã¾ãã
#!/usr/bin/env ruby # coding: utf-8 require 'simple-oauth' require 'rubygems' require 'json' # ãããç½®ãæãã CONSUMER_KEY = 'CONSUMER-KEY' CONSUMER_SECRET = 'CONSUMER-SECRET' TOKEN = 'ACCESS-TOKEN' TOKEN_SECRET = 'ACCESS-TOKEN-SECRET' simple_oauth = SimpleOAuth.new(CONSUMER_KEY, CONSUMER_SECRET, TOKEN, TOKEN_SECRET) # Tweetã®æ稿 response = simple_oauth.post('http://twitter.com/statuses/update.json', { :status => "ããã«ã¡ã¯ï¼ãã®æ稿ã¯ãã¹ãã§ãã : #{Time.now}" }) raise "Request failed: #{response.code}" unless response.code.to_i == 200 # TimeLineã®åå¾ response = simple_oauth.get('http://twitter.com/statuses/friends_timeline.json?count=5') raise "Request failed: #{response.code}" unless response.code.to_i == 200 JSON.parse(response.body).each do |status| puts "#{status['user']['screen_name']}: #{status['text']}" end
ã³ã³ã·ã¥ã¼ããã¼ãã¢ã¯ã»ã¹ãã¼ã¯ã³ã¯äºåã«åå¾ãã¦ããããã®ã¨ç½®ãæãã¦ãã ããã
ãããå®è¡ããã¨ãTweetãä¸ã¤æ稿ãããã¾ãã¿ã¤ã ã©ã¤ã³ä¸ã®ææ°5件ã®ã¤ã¶ããã表示ããã¾ãã
çåç¹
ã¨ããããåãããã«ã¯ãªã£ããã®ã®ãå®ã¯ã¾ã ã¡ãã£ã¨ä»æ§ã®ç解ã«ãããµããªé¨åããããã§ãããâ¦ã
ããã¯URLä¸ã®ã¯ã¨ãªãã¼ã¿ã¨ãªã¯ã¨ã¹ãããã£ã®æ±ãã§ãã
ããã¾ã§è¦ã¦ãã¦ãããéãããããã¯Signature Base Stringã®çæã®ããã«ä¸æ¦å解ããå¿ è¦ãããã®ã§ããããã®éãå¤ã®åã¨ã¹ã±ã¼ãã¾ã§ããå¿ è¦ãããã®ãã©ããããããã£ã¦ãã¾ããã
OAuthã®ä»æ§ã§å®ãããã¦ããunreservedãªæåã¯ãRubyã§ä¸è¬çã«ã¯ã¨ãªãã¼ã¿ã®ã¨ã¹ã±ã¼ãã«ä½¿ããã¦ããCGI#escapeã®ããã¨ã¯å¾®å¦ã«ç°ãªãã¾ãããã®ãããå¤ãã¢ã³ã¨ã¹ã±ã¼ããã¦å度ã¨ã¹ã±ã¼ãããå ´åã¨ããã§ãªãå ´åã¨ã§ãçæããããã¤ã¸ã§ã¹ããä¸è´ããªããã¿ã¼ã³ãåºã¦ãã¾ãã
ãã¡ãããåã¨ã¹ã±ã¼ãããå¿ è¦ãããã®ã§ããã°ããã¤ã¸ã§ã¹ãã¨å®ãã¼ã¿ã«å·®ç°ãåºãªãããã«ããããããªã¯ã¨ã¹ãä¸ã®ã¯ã¨ãªãã¼ã¿ã¨ãªã¯ã¨ã¹ãããã£ãåã¨ã¹ã±ã¼ããããã®ã«ç½®ãæããå¿ è¦ãããã¾ãã
ãã¤ã¸ã§ã¹ãã®çæã«ä½¿ã£ããã¼ã¿ã¨å®éã®ã¯ã¨ãªãã¼ã¿ã»ãªã¯ã¨ã¹ãããã£ã«å·®ç°ãããªããã°åé¡ãªãã ãã(=åã¨ã¹ã±ã¼ãã¾ã§ã¯è¦ããªãã ãã)ãã¨ã¯æãã®ã§ããããã®è¾ºããåç¥ã®æ¹ãããã£ãããã¾ãããæ¯éãæ示ãã ããã
ã¾ã¨ã
以ä¸ã§ãTwitterã®APIã使ãããã«æä½éå¿ è¦ãªæ©è½ãæã£ãOAuthã©ã¤ãã©ãªã*5ã¯ã²ã¨ã¾ãå®æã§ãã
ä»åæ¸ããã³ã¼ãã¯GitHubã«ç½®ãã¦ããã¾ããã
ä¸èº«ã¯ãã®è¨äºã«æ¸ãã¦ãããã®ã¨åä¸ã§ãããåå²ãããã³ã¼ãããã¡ãã¡ã³ããããã®ãé¢åèãï¼ãã¨ããå ´åã¯ãã¡ãããåå¾ãã¦ãã ããã
å¾ã¯ãããJRuby for GAE/Jç¨ã«æ¸ãç´ãããããã§ãããããã¯ã¾ãé·ããªããããªã®ã§æ¬¡ã®æ©ä¼ã«ã