ちゃなべの備忘録

ほぼ備忘録です。

認証系について調べてみた (仕組み編)【備忘録】

はじめに

前回認証系のイメージ(概要)について調べた。

ayumu1212.hatenablog.com

「じゃあこれで実装できるね!」とはならない。
実際のOIDCの仕組みなどを理解しないことには始まらない。

今回はそれを調べる

調査

OpenID Connectのざっくりフロー

qiita.com

  • 実装にはサービスサーバーとIdPサーバーが必要っぽいな。
  • 認証の暗号化とか難しいことは抜きにして、以下のフローで実装するっぽい
    1. クライアントがサービスに「外部IdPでログインしたい」と伝える
    2. サービスがIdPサーバーへのリダイレクトURLをクライアントに伝えて、リダイレクトさせる
    3. リダイレクト先のIdPサーバーが提示した認証と認可の画面でクライアントは許可する
    4. 許可されたIdPはサービスへのリダイレクトURLと認可コードを与えて、リダイレクトさせる
    5. 認可コードを受け取ったサービスはIdPサーバーに認可コードを伝える
    6. IdPサーバーがアクセストークンとIDトークンをサービスに返す
    7. このIDトークンを検証するために、IdPサーバーにアクセスして、鍵(クライアントシークレットor公開鍵)を入手する
    8. 検証ができたら、「IdPによりユーザーが認証された」ことを確認できる

OAuthのざっくりフロー

qiita.com

  • 「ユーザー情報」を教えてくれるAPIがあるとする
  • もし誰にでもユーザー情報を教えてくれるAPIだったら大変だ
  • だから「悪意のないユーザー」であることを伝える必要がある
  • そのために アクセストーク がある。「悪意がないよ!」と伝えるもの。
  • そしてそのアクセストークンを発行するための 認可サーバー も必要。
  • アクセストークンを発行する条件はユーザー認証
  • このアクセストークンを発行する流れのプロトコルがOAuth

OpenID Connectのざっくりフロー (Part2)

qiita.com

  • さっきのOAuthのフローとほぼ一緒
  • だけど、発行するのは「アクセストークン」ではなく「IDトークン」
  • つまりOIDCはOAuthを拡張したもの
  • じゃあIDトークンってなに?
  • ざっくりいうと、「ユーザーが認証されたという事実とそのユーザーの属性情報を、捏造されていないことを確認可能な方法で、各所に引き回すためにあるトークン」

IDトークンってなに?

qiita.com

  • IDトークンの外観
    • IDトークンは以下のような文字列。ピリオドで3つに別れている。
eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogImlz
cyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4
Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAi
bi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEz
MTEyODA5NzAsCiAibmFtZSI6ICJKYW5lIERvZSIsCiAiZ2l2ZW5fbmFtZSI6
ICJKYW5lIiwKICJmYW1pbHlfbmFtZSI6ICJEb2UiLAogImdlbmRlciI6ICJm
ZW1hbGUiLAogImJpcnRoZGF0ZSI6ICIwMDAwLTEwLTMxIiwKICJlbWFpbCI6
ICJqYW5lZG9lQGV4YW1wbGUuY29tIiwKICJwaWN0dXJlIjogImh0dHA6Ly9l
eGFtcGxlLmNvbS9qYW5lZG9lL21lLmpwZyIKfQ.rHQjEmBqn9Jre0OLykYNn
spA10Qql2rvx4FsD00jwlB0Sym4NzpgvPKsDjn_wMkHxcp6CilPcoKrWHcip
R2iAjzLvDNAReF97zoJqq880ZD1bwY82JDauCXELVR9O6_B0w3K-E7yM2mac
AAgNCUwtik6SjoSUZRcf-O5lygIyLENx882p6MtmwaL1hd6qn5RZOQ0TLrOY
u0532g9Exxcm-ChymrB4xLykpDj3lUivJt63eEGGN6DH5K6o33TcxkIjNrCD
4XB1CKKumZvCedgHHF3IAK4dVEDSUoGlH9z4pP_eWYNXvqQOjGs-rDaQzUHl
6cQQWNiDpWOl_lxXjQEvQ
  • 「ヘッダー.ペイロード.署名」というふうに別れている
    • この形式はJWS ( JSON Web Signature ) で定義されている
  • この3つの部分はそれぞれbase64urlでエンコードされたもの。つまりデコードすると内容がわかる。
{"kid":"1e9gdk7","alg":"RS256"}
{
 "iss": "http://server.example.com",
 "sub": "248289761001",
 "aud": "s6BhdRkqt3",
 "nonce": "n-0S6_WzA2Mj",
 "exp": 1311281970,
 "iat": 1311280970,
 "name": "Jane Doe",
 "given_name": "Jane",
 "family_name": "Doe",
 "gender": "female",
 "birthdate": "0000-10-31",
 "email": "janedoe@example.com",
 "picture": "http://example.com/janedoe/me.jpg"
}
 239 191 189 116  35  18  96 106 239 191 189 239 191 189 107 123
  67 239 191 189 239 191 189  70  13 239 191 189 239 191 189  64
 239 191 189  68  42 239 191 189 106 239 191 189 199 129 108  15
  77  35 239 191 189  80 116  75  41 239 191 189  55  58  96 239
 191 189 239 191 189 239 191 189  14  57 239 191 189 239 191 189
 239 191 189   7 239 191 189 239 191 189 122  10  41  79 114 239
 191 189 239 191 189  88 119  34 239 191 189  29 239 191 189   2
  60 203 188  51  64  69 239 191 189 125 239 191 189  58   9 239
 191 189 239 191 189  60 209 144 239 191 189 111   6  60 216 144
 218 184  37 239 191 189  45  84 125  59 239 191 189 239 191 189
 239 191 189  13 239 191 189 239 191 189  78 239 191 189  51 105
 239 191 189 112   0  32  52  37  48 239 191 189  41  58  74  58
  18  81 239 191 189  92 127 227 185 151  40   8 200 177  13 239
 191 189 239 191 189  54 239 191 189 239 191 189  45 239 191 189
   6 239 191 189 239 191 189  23 122 239 191 189 126  81 100 239
 191 189  52  76 239 191 189 239 191 189  98 239 191 189  57 239
 191 189 104  61  19  28  92 239 191 189 239 191 189 239 191 189
 239 191 189 106 239 191 189 239 191 189  18 239 191 189 239 191
 189 239 191 189 239 191 189 239 191 189  85  34 239 191 189 239
 191 189 122 239 191 189 239 191 189   6  24 222 131  31 239 191
 189 239 191 189 239 191 189 125 239 191 189 115  25   8 239 191
 189 239 191 189 239 191 189  15 239 191 189 239 191 189 239 191
 189  34 239 191 189 239 191 189 102 111   9 239 191 189  96  28
 113 119  32   2 239 191 189 117  81   3  73  74   6 239 191 189
 127 115 239 191 189 239 191 189 239 191 189 121 102  13  94 239
 191 189 239 191 189  58  49 239 191 189 239 191 189 239 191 189
 239 191 189  67  53   7 239 191 189 239 191 189  16  65  99  98
  14 239 191 189 239 191 189 239 191 189 239 191 189 113  94  52
   4 239 191 189  10
  • 3つめの署名は1つめのヘッダーのalgの値によって暗号化方法が異なる。
  • この署名が正しさを証明したいのは、「ヘッダー」と「ペイロード」の2つ
  • 署名がない「ヘッダー.ペイロード.」という形式の Unsecured JWS というのもある

  • IDトークンには「ヘッダー.キー.初期ベクター.暗号文.認証タグ」という5つに別れた形式もある

    • JWE (JSON Web Encryption) で定義している形式
  • これはIDトークンを暗号化したいときに利用される
  • OIDCの文脈だと4つめの暗号文の中に、「ヘッダー.ペイロード.署名」(つまりJWS)が入っている。
  • 2段階に暗号化を行っている
    • 対称性鍵暗号の共有鍵を生成する
    • 平文を共有鍵で暗号化する
    • 共有鍵を非対称鍵暗号の公開鍵で暗号化する
    • 暗号化した平文と共有鍵を復号化する人に渡す
    • 共有鍵を手元の秘密鍵で復号する
    • 復号した共有鍵で暗号化した平文を復号化する
  • 上記のフローに必要な情報はそれぞれ以下のように送られる
    • 暗号化された平文:JWE形式の4つめ
    • 平文を暗号化した共有鍵の形式:JWE形式の1つめのヘッダー内の enc keyのvalue
    • 暗号化された共有鍵:JWE形式の2つめ
    • 共有鍵を暗号化した形式:JWE形式の1つめのヘッダー内の alg keyのvalue
  • では、非対称鍵暗号を使う場合は公開鍵を暗号化する側に渡さなければいけません。
  • その仕様がJWK ( JSON Web Key ) です。

    • 使用はJWK Set Documentにかかれている
      • JWKの集合が書かれている
      • どう使うか、何に使ったほうがいいのか、などが書かれている
      • これは、JWKの公開鍵をまとめたもの
    • 使い方
      1. 最終的にIDトークンやアクセストークンを受け取ったサービス側がIDトークンやアクセストークンを検証するために、公開されているJWKを用いる
      2. 共有鍵を暗号化するときに、JWK Set Documentを見て、公開鍵を取得して、共有鍵を暗号化する
    • JWK Set Documentのありかの伝え方
      1. クライアントアプリの属性 jwks の値としてJWK Set Document自体が登録されている
      2. クライアントアプリの属性 jwks_uri の値としてJWK Set Documentが置かれている場所が登録されている
  • いままで説明してきた、暗号化する平文 ( JWSのペイロードや、JWEの暗号文など ) にも仕様があります。

  • それが JWT ( JSON Web Token ) です。
    • 定義:JWT とは、JSON 形式で表現されたクレーム (claim) の集合を、JWS もしくは JWE に埋め込んだもの
    • また、IDトークンの文脈におけるJWEは、「JWEの中にJWSがある」と言いましたね?ということはJWTがネストされているわけです。
    • このコトをNested JWTといいます。
    • JWTはクレームの集合なのですが、形式的には以下のようにキー・バリューのペアとして表現されています。
{
    "クレーム名": クレーム値,
    "クレーム名": クレーム値,
    ......
}
  • ここまで話してきた内容を踏まえると、 「暗号化されたIDトークンは『JWSがJWEに含まれている形のNested JWT」 です
  • 最後にIDトークンのクレームを見ていきましょう
    • iss クレーム
      • iss クレームは、JWT の発行者 (issuer) を識別するための識別子です
      • valueは文字列かURI
      • この値は他者の識別子との衝突を避けるために、自分の管理化にあるドメイン名のURLとすべき
      • 更に、OIDC ver1の仕様に乗っ取るのであれば、「{issクレームの値}/.well-known/openid-configuration」という URL でリクエストを受け付ける必要があることを念頭に置いて iss クレームの値を決める必要があります。
    • sub クレーム
      • sub クレームはユーザーの一意識別子を表す
      • IDトークンは「ユーザー認証」された後発行するものなので、そのユーザーを表す値がsub クレームです
    • aud クレーム
      • aud クレームはこのJWTの受け取り手が誰であるべきかを表すもの
      • IDトークンの場合、発行を依頼したクライアントアプリのクライアントIDです。
    • exp クレーム
      • exp クレームはJWTの有効期限を表す
      • IDトークンの場合、Unixエポック (1970年1月1日(世界標準時)) からの経過秒数となっています。ミリ秒ではないよ、秒だよ。
    • iat クレーム
      • iat クレームはJWTが発行された日時を表す
      • IDトークンの場合、exp クレームと同様にUnixエポックからの経過秒数です
    • auth_time クレーム
      • auth_time クレームはユーザーが認証された時刻を表す
      • Unixエポックからの経過秒数で表す
      • 「さっきの別のリクエストで認証したから、認証処理はパスしてIDトークン発行するヨン」ってときは auth_timeiat が異なる
      • この値は任意だが、認証後許容経過時間などでバリデーションがかかっている場合、必須となる
    • nonce クレーム
      • 主にリプレイアタックを防ぐ目的で、IDトークン発行依頼に nonce というリクエストパラメータがついてくることがあります。このときに、IDトークン発行側は、受け取ったnonce の値をそのままIDトークンに埋め込みます。
    • acr クレーム
      • acr クレームはユーザー認証が満たした認証コンテキスト ( パスワードによるシルバーレベルの認証、とか) のクラスを表す
      • 例) 値が "urn:mace:incommon:iap:silver" の場合、ユーザーはシルバーレベルの認証を受けたことを示します。
    • amr クレーム
      • amr クレームは認証手法を表している
      • パスワードベースの認証、ワンタイムパスワード、バイオメトリック認証など、さまざまな認証方法を示すことができます。
      • 値が ["pwd", "mfa"] の場合、ユーザーはパスワード認証とマルチファクタ認証を併用して認証されたことを示します。
    • azp クレーム
      • azp クレームは認可された対象者を表しています。
      • つまり認可されたクライアントアプリケーションのクライアント ID の値です。
      • このクレームは、ID トークンの発行を依頼したクライアントアプリケーションと認可されたクライアントアプリケーションが異なる場合、必要となります。

OAuthのクライアント認証とは

qiita.com

  • クライアント認証は、クライアントIDとクライアントシークレットを用いて認証を行う
    • クライアントID:アプリケーションの一意の識別子で、通常は公開情報
    • クライアントシークレット:クライアント自身を証明する秘密鍵

※ あんまり概要をつかめなかったのでまた今度

OAuthの全フロー

qiita.com

  • 4つの認可フローがある
  • 認可コードフロー
    • 基本的な認可フロー
      1. 認可サーバー内の認可エンドポイントに認可リクエストを投げて「認証情報が入力できる認可画面」を表示してもらう
      2. 認可サーバー内の認可決定エンドポイントで認証情報をもとに認可コードを発行する
      3. 認可サーバー内のトークンエンドポイントで認可コードをもとにアクセストークンを発行する
      4. アクセストークンさえ持てれば、APIを通じて許可された情報を取得できる
  • インプリシットフロー
    • 認可コードフローよりもフローが短い。「認可コード→アクセストークン」のフローが無い。直接アクセストークンを発行する
      1. 認可サーバー内の認可エンドポイントに認可リクエストを投げて「認証情報が入力できる認可画面」を表示してもらう
      2. 認可サーバー内の認可決定エンドポイントで認証情報をもとにアクセストークンを発行する
      3. アクセストークンさえ持てれば、APIを通じて許可された情報を取得できる
  • リソースオーナー・パスワード・クレデンシャルズフロー
    • インプリシットフローよりも短い。「認可リクエスト→認可画面表示」のフローがない。認可画面をサービス側に委託してしまうもの。
      1. サービス側で「認証・認可情報」を入力・選択する。
      2. 認可サーバー内のトークンエンドポイントで認証情報をもとにアクセストークンを発行する
      3. アクセストークンさえ持てれば、APIを通じて許可された情報を取得できる
  • クライアント・クレデンシャルズフロー
    • リソースオーナー・パスワード・クレデンシャルズフローよりも短い。「認可・認証情報の入力・選択」がない。"ユーザー自身の証明"がいらず、"サービスの証明"を認可サーバーに送る。
      1. 認可サーバー内のトークンエンドポイントで「クライアントIDとクライアントシークレット」をもとにアクセストークンを発行する
      2. アクセストークンさえ持てれば、APIを通じて許可された情報を取得できる
  • リフレッシュトークンフロー
    • 認可コードフローかリソースオーナー・パスワード・クレデンシャルズフローは、アクセストークンとともに 「 リフレッシュトーク 」というものも発行できる。もしそれを持っていた場合、このフローではリフレッシュトークンをトークンエンドポイントに渡すだけでアクセストークンを取得できる。
      1. 認可サーバー内のトークンエンドポイントで「リフレッシュトークン」をもとにアクセストークンを発行する
      2. アクセストークンさえ持てれば、APIを通じて許可された情報を取得できる

OpenID Connectの全フロー

qiita.com

  • OIDCはOAuth2を拡張したもの
    • OAuth2は アクセストーク 発行手順に関する仕様
    • OIDCは IDトーク の発行手順に関する仕様
  • それぞれの発行する場所は 認可エンドポイント というところ。そこでresponse_typeを要求する
    • code
    • token
    • id_token
  • 以下はresponse_typeのによる場合わけした説明である
  • response_type=code
    • scopeにopenidが含まれていないと認可コードフロー
    • scopeにopenidが含まれているとアクセストークンに加えてIDトークンも発行される
      1. 認可サーバー内の認可エンドポイントに認可リクエストを投げて「認証情報が入力できる認可画面」を表示してもらう
      2. 認可サーバー内の認可決定エンドポイントで認証情報をもとに認可コードを発行する
      3. 認可サーバー内のトークンエンドポイントで認可コードをもとにアクセストークンとIDトーク を発行する
  • response_type=token
    • インプリシットフローそのもの!scopeにopenidが含まれていようといまいと変わらない。
    • つまりこれ、OIDCだと使わない。
  • response_type=id_token
    • インプリシットフローのIDトークンだけ発行バージョン。アクセストークンは発行しない。
    • 一応以下フロー
      1. 認可サーバー内の認可エンドポイントに認可リクエストを投げて「認証情報が入力できる認可画面」を表示してもらう
      2. 認可サーバー内の認可決定エンドポイントで認証情報をもとにIDトーク を発行する
  • response_type=id_token token
    • インプリシットフローのアクセストークンとIDトークンの両方発行バージョン。
    • 一応以下フロー
      1. 認可サーバー内の認可エンドポイントに認可リクエストを投げて「認証情報が入力できる認可画面」を表示してもらう
      2. 認可サーバー内の認可決定エンドポイントで認証情報をもとにアクセストークンとIDトークンを発行する
  • response_type=code id_token
    • 「OIDCのresponse_type=id_token のフロー」と「OAuthの認可コードフロー」を同時に行うもの
      1. 認可サーバー内の認可エンドポイントに認可リクエストを投げて「認証情報が入力できる認可画面」を表示してもらう
      2. 認可サーバー内の認可決定エンドポイントで認証情報をもとに認可コードとIDトーク を発行する
      3. 認可サーバー内のトークンエンドポイントで認可コードをもとにアクセストークンを発行する
  • response_type=code token (scopeにopenidがあり)
    • 「OIDCのresponse_type=code (scopeにopenidあり)のフロー」のアクセストークンだけ認可決定エンドポイントからもらうタイプ
      1. 認可サーバー内の認可エンドポイントに認可リクエストを投げて「認証情報が入力できる認可画面」を表示してもらう
      2. 認可サーバー内の認可決定エンドポイントで認証情報をもとに認可コードとアクセストークンを発行する
      3. 認可サーバー内のトークンエンドポイントで認可コードをもとにIDトークンを発行する
  • response_type=code token (scopeにopenidがない)
    • 認可決定エンドポイントとトークンエンドポイント両方でアクセストークンを発行するタイプ。IDトークンは発行しない。
      1. 認可サーバー内の認可エンドポイントに認可リクエストを投げて「認証情報が入力できる認可画面」を表示してもらう
      2. 認可サーバー内の認可決定エンドポイントで認証情報をもとに認可コードとアクセストークンを発行する
      3. 認可サーバー内のトークンエンドポイントで認可コードをもとにアクセストークンを発行する
    • 2つのアクセストークンは同じじゃない場合がある。権限による。
  • response_type=code id_token token
    • 認可決定エンドポイントとトークンエンドポイント両方でアクセストークンとIDトークンを発行するタイプ。
      1. 認可サーバー内の認可エンドポイントに認可リクエストを投げて「認証情報が入力できる認可画面」を表示してもらう
      2. 認可サーバー内の認可決定エンドポイントで認証情報をもとに認可コードとアクセストークンとIDトークンを発行する
      3. 認可サーバー内のトークンエンドポイントで認可コードをもとにアクセストークンとIDトークンを発行する
  • response_type=none
    • 認証・認可しても認可決定エンドポイントから何も発行しない。(何がしたいん?)
      1. 認可サーバー内の認可エンドポイントに認可リクエストを投げて「認証情報が入力できる認可画面」を表示してもらう
      2. 入力してもらったけど、何も返さない。

あーーー疲れた。一旦ここまで。
また川崎さんにとてつもない感謝を。