超速習 OpenID Connect

  • 57
    Like
  • 0
    Comment
More than 1 year has passed since last update.

この記事は

この記事は ACCESS Advent Calendar 11日目の記事です。

研究開発本部開発課の梶原( @sylph01 )です。最近ID周りのことをやることが多く、今回はその中でも勉強会のネタとして発表したOpenID Connectの話をまとめました。というわけで中の人にとっては再放送です。ご了承を><


2015年といえばOpenID Connect 1.0がLaunchした年ですね!

はい、2015年2月26日にOpenID Connect 1.0のFinal SpecsがLaunchし、4月からはCertification Programも始まっています。

そういえばID連携やOpenIDとかOAuthとかSAMLとか聞いたことあるけど、なんでまた新しい規格が立ってるの?

簡単にいうと、OpenID Connectのこれらの規格に対する位置づけは

  • OpenID 2.0でやろうとしたことをもっとAPI friendlyに
  • OAuth 2.0のAPIを拡張し、認可のためのプロトコルを認証の連携にも使えるようにして
  • 結果としてXMLベースの認証のやりとりだったSAMLがよりシンプルになり、また現代的なJSONベースのプロトコルで同じこと+アルファを実現できるようにした

…という感じの位置づけです。

ここで用語を説明すると:

  • Entity = 対象
  • Identity = 属性の集合
  • Authentication(認証) = Entityがサービスの認知するIdentityに紐付いているという確証を得ること
    • 例: サービスに登録されているユーザーIDとパスワードという属性からこの対象はユーザーAであると「認証」する
  • Authorization(認可) = リソースにアクセスするための条件を定めること
    • 例: ユーザーAとして認証されている対象は管理者権限を持っているので管理者権限で操作できるAPIを操作できる(操作が「認可」される)

…という用語がIDの文脈ではよく使われます。

そしてID連携といった場合、「認証結果」を連携することでたとえばGoogleのパスワードだけ覚えていればGoogleとID連携しているサービスが使えるようになり、「属性情報の連携」をしている場合はたとえばGoogleのユーザー情報の一部があらかじめ埋まった状態で最初のユーザー登録を行うことができるなどの恩恵を受けることができます。

じゃあOpenID Connectで認証の連携って実際どうやるの?

ここではOpenID Connect CoreのAuthenticationの章を解説します。

OpenID Connectでの認証連携の方法ですが、実装しているアプリの種類によって取り得るアプローチが3つあります。

Authorization Code Flow: サーバーサイドアプリのためのフロー

AuthorizationCode.png

  • クライアントはAuthorization Endpointに認証リクエストを投げます。
    • ここでresponse_type = codeを指定します。これによって、成功するとレスポンスでAuthorization Codeを得ます。
    • ここで指定するstateはどのリクエストに対してどのレスポンスが帰ってきたか対応関係をつけるためのランダムな値で、Authorization Endpointは正しいレスポンスでは同じstate値を返します。ここで違うstate値が返ってきた場合は偽装されているということがわかるのでここで即座に処理を中止します。
  • 認証リクエストに対し、サーバー側では認証と「この属性情報を共有するけれど大丈夫か」と確認をとります(Consent)。
    • どの属性情報を要求されたかは認証リクエストのパラメータのscopeの中に含まれています。例の図でいうとopenid profile emailが含まれているのでprofileとemailに対応する値が返ります。profileが返す値の詳細についてはOpenID Connect Coreの5.4章を参照してください。
  • 認証がとれ属性共有の確認がとれたら、サーバーは認証リクエストに指定されたリダイレクトURIにリダイレクトが行われ、URLの中にcodeとstateが含まれてクライアントに処理が戻ります。
  • その後、クライアントはToken EndpointにAuthorization Codeを送り、Access Token・ID Tokenとの引き換えを行います。Codeが適切な値であると確認されたらAccess TokenとID Tokenをレスポンスとして得ることができます。
    • Access Tokenを取得するところはOAuth 2.0と似ています。
    • ID TokenはJWT(JSON Web Token)形式で表現された「署名付きトークン」です。ヘッダ { "typ" : "JWT, "alg" : "RS256" } と ペイロード と シグネチャ からなります。
      • 署名はヘッダ + ペイロードをBase64エンコード + 公開鍵暗号にかけた値です。

Base64エンコードを解くとこんな感じになります:

  {
   "iss": "https://server.example.com",
   "sub": "24400320",
   "aud": "s6BhdRkqt3",
   "nonce": "n-0S6_WzA2Mj",
   "exp": 1311281970,
   "iat": 1311280970,
   "auth_time": 1311280969,
   "acr": "urn:mace:incommon:iap:silver"
  }

そしてこれがそれぞれ以下のような意味を持っています:

  • iss* : Issuer identifier(IdP) - IdP(Identity Provider)の識別し。URIなどが入る。
  • sub* : Subject identifier(end user) - エンドユーザーの識別子。サーバー内でのエンドユーザーのID表現などが入る。
  • aud* : Audiences that this ID token is intended for(RP: Relying Party) - どのクライアント向けに発行されたトークンであるか。リクエストのclient_idで指定した値などが入る。
  • exp* : Expiration time : ID Tokenの有効期限。
  • iat* : Issued-At : IDトークンがいつ発行されたかのタイムスタンプ。
  • nonce: String used to associate a Client session w/ an ID token and to mitigate replay attacks : 最初に指定したnonce値。リプレイアタックの防止に使われる。

Implicit Flow: クライアントサイドアプリのためのフロー

Implicit.png

こちらはAuthorization Code Flowに比べてだいぶ手順が減っています。こちらでは全てのトークンはAuthorization Endpointから返されます。

Implicit Flowはクライアントサイドのスクリプト言語で書かれたアプリなどで使われますが、Access TokenとID Tokenが直接ユーザーに返されるためエンドユーザーやエンドユーザーのUser Agentにアクセスできる人に晒されてしまうことがあります。また、Implicit Flowでは対策なしではリプレイアタックが容易なためnonceの指定が必須になっています

こちらはresponse_typeid_token tokencodeを含まない)としているため、レスポンスではID TokenとAccess Tokenが返されます。

Hybrid Flow: フロントエンド・バックエンド両方で連携したいときに使うフロー

Hybrid FlowではAuthorization Code Flowと同じようなフローを辿りますが以下のような違いがあります:

  • response_typecodeを指定、それに加えてid_tokenまたはtokenを指定。また、Implicit Flowと同様にnonceの指定が必須。
  • これによってAuthorization Codeと指定したID TokenまたはAccess Tokenを得ます。
  • Implicit FlowではAccess Token/ID Tokenを受け取った時点で終了しますが、Hybrid FlowではさらにToken Requestも行います。
    • 既にTokenを得ているのだからもっかいリクエストして何が嬉しいの?ということについては以下のような違いがあります:
      • Access Tokenが違った値が返されることがあります。これは例えばAuthorization Codeと引き換えに得たAccess Tokenはセキュリティ的により強固なフローを経ているのでより長い有効期限のTokenにしよう、などというようにサーバー側で設定されている場合にありえます。
      • ID Tokenについても、認証リクエストのID Tokenで得られる属性情報はいくつかのClaimが抜けていて、完全な形の属性情報を得られるトークンをToken Requestで返す、というようなサーバー側の実装があり得る(プライバシー上の理由などから)ので、Token Requestでより完全なトークンを得られるというメリットがあります。

これだとほとんどOAuth 2.0だけど何が嬉しいの?

はい、確かにフローはかなりOAuth 2.0なのですが、わかりやすいところだと属性情報の取得エンドポイントが標準化された、ということが「ID連携」においては非常に意義深いところです。「OAuthログイン」を実装する場合、OAuthでAPIを叩く権利を得るところまでは標準化されていても、属性情報を得るためのAPIが一本化されていない、または要求するパラメータがサーバーによって違うなど、「OAuthログイン」はIdPの方言が多すぎることが実装上の問題になっていました。しかしOpenID Connectでは属性情報を得るためのUserInfo Endpointというのがあり、ここにAccess Token付きでリクエストをすることでAccess Tokenを得る際に指定したscopeの属性情報のClaimに対応する属性情報を得ることができます。「認証」には「APIの認可」+「属性情報の取得」が必須なので、これによって「認可」だけでなく「認証」も使いやすいAPIで標準化された、ということが非常に大きな意味を持っています。

今回は説明を省略しましたが、OpenID ConnectにはクライアントがIdPの情報を得るためのDiscoveryという仕様や、関連した仕様として文中でも出てきたJWTや暗号化したコンテンツをJSONで表現するJWE(JSON Web Encryption)など様々な標準仕様を用意して安全なIDのやり取りを実現できるようにしています。


明日(12/12)は @aTomoyaKubo さんの記事です。お楽しみに!