OAuth EchoのRequestTokenを取得する

Ken published on
6 min, 1040 words

Categories: Programming

Twitter絡みで、OAuth Echoを使いたかったので書いてみた。

ネットの情報を色々と見ていると、人それぞれというか、動くのかな?っていうのもあったりして、軽く振り回された感あり :| 書いたソースコードはgithubに置いておきます&下の方に貼り付けておきます。 http://gist.github.com/497821 お役に立つようでしたら、ご自由にどうぞ。

以下、RequestTokenを取得するURLの作り方を簡単にまとめます。 RequestTokenはTwitterに署名付きのパラメータを送ることによってもらうことが出来きます。

http://twitter.com/oauth/request_token?&<各種パラメータ>&<署名>

この各種パラメータは次の通り。

  • oauth_consumer_key: 各自のConsumerKey
  • oauth_signature_method: "HMAC-SHA1" 他にもあるらしいが今回はこれ固定。
  • oauth_nonce: その場限りの値。ランダム文字列を利用。
  • oauth_timestamp: 現在時刻を秒で表した値。1900年を起算にっていう例のやつ。
  • oauth_version: OAuthのバージョン値。"1.0"固定。

URL構築時にはお約束の key=value を&で連結。

署名がちょっと面倒。 まずは署名の準備。署名にはHMAC-SHA1形式を利用。 キーを作成するときは "ConsumerSecret&OAuthToken" とする。もちろん、ConsumerSecretもOAuthTokenも自分の値で書き換えてください。 初回はOAuthTokenをまだ取得してないので空文字列となり、結果的に "ConsumerSecret&" となります。

続いて、署名するための文字列を作成します。

  1. パラメータを名称順にソート。
  2. パラメータを連結(key1=value&key2=value2&...)。これを仮に<Params>とする。keyとvalueをそれぞれエンコードする必要があるという情報も見たが、今回はエンコードしても同じなので、してません。
  3. "GET&encode(http://twitter.com/oauth/request_token)&encode(<Params>)" を作成。encodeはURLエンコード。

ここで作成した "GET..." という文字列を先程作成したキーで署名します。 署名した値をBase64エンコードし、その後、URLエンコードします。 これがRequestTokenをもらうためのURLの一部となる署名です。

生成したURLを叩いて "oauth_token=..." というレスポンスが帰ってくれば成功です。

もし、うまくいかなかった場合に見直した方がいいと思われるところ。

  • 署名用のキーを生成するところ。ConsumerSecretとOAuthTokenを利用しているか?OAuthTokenが無い場合は "ConsumerSecret&" のように&で終わっているか?
  • 署名する値("GET&http://...")の生成は正しいか?
  • 署名した値をBase64→URLエンコードしているか?

それほど難しいわけじゃないけど、はまるとミスっている箇所を見つけるのがちょっと大変かも。

package oauth

import java.net.URLEncoder
import java.util.Random

import java.security.Key
import javax.crypto.spec.SecretKeySpec
import javax.crypto.Mac

import sun.misc.BASE64Encoder

object OAuthEcho {

	private val TwitterTokenURL = "http://twitter.com/oauth/request_token"

	def mkTwitterURL(consumerKey:String, consumerSecret:String, oAuthToken:String=""):String = {
		val params = Map("oauth_consumer_key" -> consumerKey,
						"oauth_signature_method" -> "HMAC-SHA1",
						"oauth_nonce" -> nonce,
						"oauth_timestamp" -> timestamp,
						"oauth_version" -> "1.0")

		val signature = sign(params, consumerSecret, oAuthToken)
		
		val oAuthParams = params.map { case(k, v) => "%s=%s".format(k, v) }.mkString("&")
		"%s?%s&oauth_signature=%s".format(TwitterTokenURL, oAuthParams, signature)
	}

	/**	署名を作成する。
	 *	キーを生成するための値は "consumerSecret&oAuthToken" であるが、oAuthTokenが存在しない場合もある。
	 *	oAuthTokenが存在しない場合は "consumerSecret&" となり "&" は必須。
	 *	署名した値はBase64エンコード後、URLエンコードする。
	 *	@params パラメータ
	 *	@consumerSecret ConsumerSecret
	 *	@oAuthToken OAuthToken
	 */
	private def sign(params:Map[String, String], consumerSecret:String, oAuthToken:String):String = {
		// 認証コード準備
		val secrets = "%s&%s".format(consumerSecret, oAuthToken)
		val key = new SecretKeySpec(secrets.getBytes("UTF-8"), "HmacSHA1")
		val mac = Mac.getInstance(key.getAlgorithm)
		mac.init(key)

		// 署名
		val unsigned = mkUnsignedURL(params)
		val digest = mac.doFinal(unsigned.getBytes)
		encode(new BASE64Encoder().encode(digest))
	}

	/**	署名を行うための文字列を作成する。
	 *	この文字列は "GET" と "エンコードしたURL" と "エンコードしたパラメータ" を "&" で接続した値。
	 *	エンコードしたパラメータは、パラメータ名でソートする必要がある。
	 *	@params パラメータ
	 */
	private def mkUnsignedURL(params:Map[String, String]):String = {
		val oAuthParams = params.toList.sort { (a, b) => a._1 < b._1 }.map { case(k, v) => "%s=%s".format(k, v) }.mkString("&")
		"GET&%s&%s".format(encode(TwitterTokenURL), encode(oAuthParams))
	}

	private def encode(value:String):String = URLEncoder.encode(value, "UTF-8")
	private def timestamp:String = (new java.util.Date().getTime / 1000).toString
	private def nonce:String = Math.abs(new Random().nextLong).toString

}