OAuthプロトコルの中身をざっくり解説してみるよ
「おーおーっすっ!」
てなこって、TwitterのAPIのBASIC認証も6月末に終了してOAuth/xAuthに移行するというこの時期に、あらためてOAuthについて勉強してみたんですのよ?
OAuth認証を利用するライブラリは各言語で出そろってきてるのでそれを使えばいんじゃまいか? というと話が終わるので、じゃあそのライブラリの中身はなにやってんのよってことを、OAuthするScalaのライブラリ作りながら調べたことをまとめてみました。
間違っているところもあると思うのでツッコミ歓迎です><
OAuthってそもそもなんなの?
BASIC認証の場合、API利用側が認証に必要なアカウントやパスワードを預かる必要があるわけです。悪意のあるAPI利用側が﹁なんとかメーカー﹂とかいうついったーのアカウントを利用するサービスを、BASIC認証を用いて実装した場合、その﹁なんとかメーカー﹂を使うユーザーのIDとパスワードを入力させて、サーバー上のデータベースに保存しておいてアカウントをハックするとか出来ちゃうわけです(インターネットこわい)。
つまり、BASIC認証はIDやパスワードが一時的にせよAPI利用側に預けられてしまうという問題です。
これを回避するために、アカウントの認証処理自体をついったーなどAPI提供側に代わりにやってもらい、API利用側は認証された結果だけをもらうという形にしたのがOAuthです。
図にするとこんな感じです。Service Provider(Provider)は、ついったーや4sqなどのAPI提供側。OAuth Consumer(Consumer)は、OAuthでProviderに認証してもらってAPIを呼び出す側(例えばふぁぼったー)。Userは、実際のアカウントを持っている人です(ようは、あなたです!)。
![f:id:yuroyoro:20100506175544p:image f:id:yuroyoro:20100506175544p:image](http://cdn-ak.f.st-hatena.com/images/fotolife/y/yuroyoro/20100506/20100506175544.png)
OAuthでは、アカウント認証でIDやパスワードを入力する箇所はついったーなどProviderのページに限定されますので、悪意のあるConsumerがIDパスワードを盗むことができなくなります。
認証のざっくりした手順
まずは、事前準備としてConsumerの登録が必要です。 (一)Consumer登録 (一)通常は、事前にProviderにConsumer登録をしておきます。 (二)登録すると、Consumer KeyとConsumer Secretが発行されます。 (三)Consumer KeyとConsumer Secretは、以降のOAuth認証で使用します
ここからが、実際のOAuth認証の流れです。 (一)ユーザは、ConsumerにOAuth認証を行うように指示します。 (一)通常は、Consumerのページにあるログインボタンなどをクリックします。 (二)Consumerは、Providerからリクエストトークンを取得します (一)ConsumerからProviderへHttp通信でリクエストトークンを要求します。 (二)リクエストトークンの要求で、事前に発行されているConsumer Keyと、リクエストパラメータをConsumer Secretで署名した値をパラメータとして付与します。 (三)ProviderはHttpのレスポンスとしてリクエストトークンを返します。この段階では、まだ認証は完了していません。 (三)認証用URLへのリダイレクト・ユーザ承認 (一)Consumerは、発行されたリクエストトークンをURLに付与して、Providerの認証用URLへリダイレクトを行います。 (二)リダイレクト先で、Providerがユーザに対して、Consumerが要求しているOAuth認証によるAPI利用を許可するか選択します。 (三)承認した場合は、Providerは(通常は)Consumer登録時に設定したコールバックURLへリダイクレトします。 (四)アクセストークンの取得 (一)コールバックURLへのリダイレクトで、Consumerはリクエストトークンをもとにアクセストークン取得をHttp通信でProviderへ要求します (二)アクセストークン取得要求は、Consumer Keyとリクエストトークンなどをパラメータに付与して呼び出します。(通常はAuthorizationヘッダに設定) (三)アクセストークン取得要求のパラメータも、Consumer Secretで署名した値を付与します。 (四)Providerは、レスポンスとしてアクセストークンを返します。 (五)OAuthでのAPI呼び出し (一)実際のAPI呼び出しは、取得したアクセストークンをパラメータに付与して呼び出します。 (二)アクセストークンとConsumer Key、およびパラメータをConsumer Secretで署名した値を、Authorizationヘッダに設定に設定してAPIを呼び出すことで、OAuth認証を利用したAPI呼び出しができます。
ざっくり図にするとこんな感じでしょうか?
![f:id:yuroyoro:20100506190425p:image f:id:yuroyoro:20100506190425p:image](http://cdn-ak.f.st-hatena.com/images/fotolife/y/yuroyoro/20100506/20100506190425.png)
まとめます。
OAuthの認証からAPI呼び出しまで
じゃあ、実際のOAuth認証からAPI呼び出しまでの間に、Consumer/Provider/Userの3者間でどのようなやりとりが行われているのか解説しますね。
0.Consumer登録
TwitterのConsumer登録ページ foursquereのConsumer登録ページ
Consumer登録を行うと、Consumer KeyとConsumer Secretという二つの値が発行されます。
Consumer Keyは、﹁1.リクエストトークンの取得﹂や﹁3.アクセストークンの取得﹂や﹁4.API呼び出し﹂の呼び出しなど、OAuthに関する全てのProviderへのリクエストで必要とされるパラメータで、いわばConsumerのIDになります。
Consumer Secretは、OAuth通信で送信するパラメータが改竄されていないか確認するための署名を生成するために必要な、いわば秘密鍵です。 署名に関しては、﹁9.リクエストの署名﹂で解説します。
1.リクエストトークンの取得
![f:id:yuroyoro:20100506190427p:image f:id:yuroyoro:20100506190427p:image](http://cdn-ak.f.st-hatena.com/images/fotolife/y/yuroyoro/20100506/20100506190427.png)
リクエストトークンの発行要求は、リクエストトークン発行用のProvider側のURLへ通常HTTP POSTを送信することで行います。 ここで、OAuthパラメータとして、以下のような値を設定します。
oauth_Consumer_key | Consumer登録時に発行されたConsumer Key |
---|---|
oauth_timestamp | リクエスト作成時のタイムスタンプ値 |
oauth_nonce | リクエスト毎に一意な値。通常はナノ秒を設定 |
oauth_signature | 送信するURLやパラメータなどが改竄されていないか確認するためにConsumer側で生成された値。通常はHMAC-SHA1でConsumer Secretを元にダイジェストを生成しBase64でエンコードする。詳しくは「9.リクエストの署名」を参照。 |
oauth_signature_method | oauth_signatureを生成する署名方式。通常はHMAC-SHA1だが、PLAINTEXTをサポートするProviderもある |
oauth_version | 必須ではないが、設定する場合は1.0である必要がある |
これらのパラメータを、HttpヘッダのAuthorizationヘッダにつけてPOSTします。具体的なリクエストヘッダの中身はこんな感じです。
POST /oauth/request_token HTTP/1.1 Authorization: OAuth oauth_consumer_key=XXXX&oauth_nonce=1111&oauth_signature=YYYY=&oauth_signature_method=HMAC-SHA1&oauth_timestamp=9999
なお、AuthorizationヘッダではなくPOSTのBodyに設定してもOKな場合もあります。
さて、このPOSTのレスポンスとして、Provider側からレスポンスボディにリクエストトークンの値が設定されて返されます。
oauth_token=XYZABCD&oauth_token_secret=ZZZZZ
oauth_token | これがリクエストトークンの値 |
---|---|
oauth_token_secret | リクエストトークン毎に発行される値。アクセストークン発行時の署名はこの値を含めたキーにより生成する必要がある。詳しくは「9.リクエストの署名」を参照。 |
ここまででリクエストトークンの取得が終わりました。
2.認証用URLへのリダイレクト・ユーザ承認
![f:id:yuroyoro:20100506191220p:image f:id:yuroyoro:20100506191220p:image](http://cdn-ak.f.st-hatena.com/images/fotolife/y/yuroyoro/20100506/20100506191220.png)
通常のWebサービスでは、ユーザのOAuth認証ボタンのクリック時にリクエストトークンを取得して、そのレスポンスとしてユーザのブラウザにProviderの認証用URLへリダイクレトを返す形になるでしょう。
Providerの認証用URLへは、リクエストトークンをURLパラメータに含めたURLでのリダイレクトになります。たとえば、"http://foursquare.com/oauth/authorize?oauth_token=XYZABCD"のような形です。
リダイレクト先のProviderは、このConsumerからのOAuth認証要求をユーザが承認するか確認するページを返します。foursquerの場合はこんな画面です。
![f:id:yuroyoro:20100506175548p:image f:id:yuroyoro:20100506175548p:image](http://cdn-ak.f.st-hatena.com/images/fotolife/y/yuroyoro/20100506/20100506175548.png)
この画面で承認を行うと、ProviderはあらかじめConsumer登録時に設定されているURLへリダイクレトを行います。このときにパラメータとしてリクエストトークンの値と、場合によってはoauth_verifierという値が付与されます。
こんな感じのURLになります。
http://fooservice.com/?oauth_token=XYZABC&oauth_verifier=NNNNNN
3.アクセストークンの取得
![f:id:yuroyoro:20100506190429p:image f:id:yuroyoro:20100506190429p:image](http://cdn-ak.f.st-hatena.com/images/fotolife/y/yuroyoro/20100506/20100506190429.png)
アクセストークンも、リクエストトークンと同じようにProvider指定のURLへHTTP通信を行うことで取得できます。
OAuthパラメータとして、以下のような値をAuthorizationヘッダに設定します。
oauth_token | ユーザが承認済みのリクエストトークン |
---|---|
oauth_verifier | リクエストトークンが承認された時にもらえるoauth_verifierの値 |
oauth_consumer_key | Consumer登録時に発行されたConsumer Key |
oauth_timestamp | リクエスト作成時のタイムスタンプ値 |
oauth_nonce | リクエスト毎に一意な値。通常はナノ秒を設定 |
oauth_signature | 送信するURLやパラメータなどが改竄されていないか確認するためにConsumer側で生成された値。通常はHMAC-SHA1でConsumer Secretを元にダイジェストを生成しBase64でエンコードする。詳しくは「9.リクエストの署名」を参照。 |
oauth_signature_method | oauth_signatureを生成する署名方式。通常はHMAC-SHA1だが、PLAINTEXTをサポートするProviderもある |
oauth_version | 必須ではないが、設定する場合は1.0である必要がある |
リクエストトークン発行時のパラメータにくわえて、oauth_tokenとoauth_verifierが追加されています。
具体的なHTTPリクエストヘッダはこんな感じです。
POST /oauth/access_token HTTP/1.1 Authorization: OAuth oauth_consumer_key=XXXX&oauth_nonce=1111&oauth_signature=YYYY=&oauth_signature_method=HMAC-SHA1&oauth_timestamp=9999&oauth_token=XYZABC&oauth_verifier=NNNNNN
Provider側のレスポンスも、同様にレスポンスボディにアクセストークンの値が設定されています。
oauth_token=EFGHIJ&oauth_token_secret=WWWWW
oauth_token | これがアクセストークンの値 |
---|---|
oauth_token_secret | アクセストークン毎に発行される値。以降のAPI呼び出し時には、この値を含めたキーにより署名を生成する必要がある。詳しくは「9.リクエストの署名」を参照。 |
これでようやくOAuthでAPIを呼び出す準備ができました。
4.API呼び出し
![f:id:yuroyoro:20100506190430p:image f:id:yuroyoro:20100506190430p:image](http://cdn-ak.f.st-hatena.com/images/fotolife/y/yuroyoro/20100506/20100506190430.png)
OAuth認証とBasic認証のAPI呼び出しの違いは、Authorizationヘッダの違いと言ってもよいかと思います。
例えば、foursquerのcheckinsというAPI呼び出しのリクエストヘッダは以下のようになります。
GET /v1/checkins HTTP/1.1 Host: api.foursquare.com Authorization: OAuth oauth_consumer_key=XXXX&oauth_nonce=1111&oauth_signature=YYYY=&oauth_signature_method=HMAC-SHA1&oauth_timestamp=9999&oauth_token=XYZABC&oauth_verifier=NNNNNN
具体的に、Authorizationヘッダに設定する値は以下の通りです。
oauth_token | アクセストークンの値。 |
---|---|
oauth_consumer_key | Consumer登録時に発行されたConsumer Key |
oauth_timestamp | リクエスト作成時のタイムスタンプ値 |
oauth_nonce | リクエスト毎に一意な値。通常はナノ秒を設定 |
oauth_signature | 送信するURLやパラメータなどが改竄されていないか確認するためにConsumer側で生成された値。通常はHMAC-SHA1でConsumer Secretを元にダイジェストを生成しBase64でエンコードする。詳しくは「9.リクエストの署名」を参照。 |
oauth_signature_method | oauth_signatureを生成する署名方式。通常はHMAC-SHA1だが、PLAINTEXTをサポートするProviderもある |
oauth_version | 必須ではないが、設定する場合は1.0である必要がある |
9.リクエストの署名
ぶっちゃけ、ここ見てもらうのが早いんですがまぁ軽く書いてみます。
署名方式は、通常は、HMAC-SHA1という方式です。
署名は﹁(A)Consumer Secret及びToken Secret﹂をキーとして、﹁(B)HTTPメソッド、URL、全てのパラメータを連結した文字列﹂を元に作成されるダイジェスト値です。
まず﹁(A)Consumer Secret及びToken Secret﹂の署名キーですが、以下の形式の文字列をキーとします。
"Consumer SecretをURLエンコードした値"&"Token Secretの値"
Consumer SecretはConsumer登録時にProviderから発行される値、Token Secretは、リクエストトークンやアクセストークンが発行されたときにProviderのHTTPレスポンスボディに含まれる値です。
Consumer Secret=XXXXでTokenSecretがNNNNの場合は、キーは"XXXX&NNNN"です。
﹁1.リクエストトークンの取得﹂の時点ではToken Secretはまだ無いので、Consumer Secretのみで署名キーを作ります。"XXXX&"のようになります。
次に﹁(B)HTTPメソッド、URL、全てのパラメータを連結した文字列﹂についてです。
"(a)HTTPメソッド"&"(b)アクセスするURL"&"(c)全てのクエリパラメータをキーの昇順でソートしURLエンコードした値"(a)HTTPメソッドは、GET/POSTなどです。(b)は実際にアクセスするURL(http://foursquare.com/oauth/request_token など)です。
(c)全てのクエリパラメータをキーの昇順でソートしURLエンコードした値については、OAuthパラメータを含む全てのパラメータ(APIコール時のパラメータも含む)を、キーの昇順にソートした上で"キー1=値1&キー2=値2..."のように&で結合します。
例:
param1=value1¶m2=value2&oauth_consumer_key=XXXX&oauth_nonce=1111&oauth_signature=YYYY=&oauth_signature_method=HMAC-SHA1&oauth_timestamp=9999&oauth_token=XYZABC&oauth_verifier=NNNNNN
最後に、(a)と(b)と(c)をそれぞれURLエンコードして"&"で結合します。これでできた文字列が署名対象です。
例:
POST&http:%3A%2F%2Ffoursquare.com%2Foauth%2Frequest_token¶m1=value1%3param2=value2%3oauth_consumer_key=XXXX%3oauth_nonce=1111%3oauth_signature=YYYY=%3oauth_signature_method=HMAC-SHA1%3oauth_timestamp=9999%3oauth_token=XYZABC%3oauth_verifier=NNNNNN
実際の署名は(A)署名キーをもとに(B)署名対象文字列からHMAC-SHA1アルゴリズムを利用して16進のダイジェスト値を生成し、その値をBase64エンコードします。さらに、URLエンコードした文字列が署名としてoauth_signatureの値になります。
実際に生成したものがこれです。
8bP1EEnRivY3cDkYHcqaLN7+wRM=
ScalaでOAuthライブラリ書いてみた
てきとーじっそうです。
yuroyoro-util/src/main/scala/com/yuroyoro/util/net/OAuth.scala at master · yuroyoro/yuroyoro-util · GitHub
Scalaでは、Dispatchというのがあります。
Dispatch — Dispatch