はじめに
記事を閲覧いただきありがとうございます。普段は仕事でSpring Bootを用いたバックエンドと、React・React NativeなどによるWeb/モバイルアプリの開発を行っています。今回、突然の長期休暇をもらったのを機に、以前から理解の浅さを感じていた「認証・認可」を改めて学び直すことにしました。昨年も書籍でOAuthやOIDCを学び、自身の手でログイン機能は実装できるようになっていましたが、他者から質問を受けた際やSpring SecurityのFilterChainをカスタムする場面では知識不足を痛感していました。そこで今回は「Security FilterChainを自分でカスタムできる」ことを目標に学習し、その過程を備忘録としてまとめます。
本記事の内容
本記事では、認証・認可のうち「認証」をテーマに、学んだことを整理します。「認証」については以前の記事で学びをまとめていますので、ご参考にしてください。OIDCについて触れますが、認証まわりの詳細な仕様やコード解説は、すでに基本を理解していることを前提として省略しますので、必要に応じて書籍や公式ドキュメントを参照してください。
学習に使用した書籍について
今回の学習では、こちらの書籍を活用しました。OAuthで登場するさまざまなグラントタイプについて、シーケンス図を交えて丁寧に解説されており、フローの全体像がつかみやすい一冊です。OAuthをこれからしっかり理解したい方には、ぜひ手に取って読んでみることをおすすめします。
認証という言葉に関する誤認識
以前、OAuthに関する書籍を読んだ際、OAuthは認可のためのフレームワークであることを学びました。
OAuthの仕組みを使うと、アクセストークンを用いてリソースサーバーからユーザーの情報も取得することができます。しかし、このことをもって「認証できた」と考えてしまうのは正しい理解ではありません。実際、世間では「OAuth認証」という呼び方を耳にすることがありますが、書籍ではこの言葉を紹介しつつも、本来の認証という観点では脆弱であると指摘しています。
では、その脆弱性とは何でしょうか。
ライブチケットを例に考えてみましょう。チケットはライブを鑑賞するための「認可」を示すものです。しかし、このチケットを他人が入手した場合、現在の仕様ではチケットに記載された権限だけが確認されるため、本人であるかどうかに関係なく会場に入れてしまいます。
アクセストークンも同様です。アクセストークンは「何ができるか」を示しますが、「誰のものか」は保証しません。そのため、本当に本人であるかを確認する仕組み――これこそが認証――が必要になります。
OpenID Connect(OIDC)
ここで登場するのがOpenID Connect(OIDC)です。OIDCは本人確認を行うための仕組みをOAuthの上に追加し、IDトークンを用いて確実に認証できるようにしています。
OIDCに関する詳細な仕様は今回も割愛しますが、以下に備忘録としてキーワードとその意を整理して本記事は終わりとします。
OAuthとOIDCの違い
ロール
書籍上の呼び方ですが、対応を示します。
OAuth | OIDC |
---|---|
リソースオーナー | エンドユーザー |
クライアント | リクライニング・パーティ |
認可サーバー | IDプロバイダ |
リソースサーバー | UserInfoエンドポイント |
グラントタイプとフロー
グラントと呼ばれていたものはフローに変わるほか、OIDCでは存在しないものがあります。
OAuth | OIDC |
---|---|
認可コードグラント | 認可コードフロー |
インプリシットグラント | インプリシットフロー |
クライアントクレデンシャルグラント | - |
リソースオーナパスワードクレデンシャルグラント | - |
ハイブリットフロー | ハイブリットフロー |
スコープ
OAuthではスコープの形式や権限についての規定はないが、OIDCでは明確に規定があります。
値 | 取得できる情報 |
---|---|
openid | (必須) OIDCのリクエストであることを表す |
profile | 名前、誕生日、性別、写真などのプロフィール情報へのアクセス |
emailおよびemail_verifiedへのアクセス | |
address | addressへのアクセス |
phone | phone_numberおよびphone_number_verifiedへのアクセス |
IDトークン
IDトークンとは
リクライニング・パーティがエンドユーザーを認証するためのもので、IDプロバイダが発行します。
トークンが発行されるタイミングはアクセストークンが発行されるタイミングと同じです。
JWT
IDトークンは署名付きのJSON Web Token(JWT)です。JWTは「ヘッダー」「ペイロード」「署名」で構成されており、それぞれドットで区切られています。それぞれの役割についての説明は割愛します。
JWTの構造
<ヘッダー>.<ペイロード>.<署名>
JWK
JWT周りで理解の不足を感じていたのが署名の検証に関する内容です。認証においてはクライアント(Relying Party)はIDトークンを受け取ると、まず署名の検証を行い、「このトークンが確かにIDプロバイダによって発行されたもので、途中で改ざんされていない」ことを確認します。この署名検証には、IDプロバイダが持つ秘密鍵で署名されたトークンを、対応する公開鍵で検証する仕組みが使われます。OIDCでは、IDプロバイダはこの公開鍵をJWK形式で公開しており、複数の鍵(キーID kid 付き)をまとめたJWK SetをHTTPエンドポイントから配信しています。
例えばGoogleの場合、以下のURLで公開鍵セットが取得できます。
{
"keys": [
{
"kid": "ba63b436836a939b795b4122d3f4d0d225d1c700",
"e": "AQAB",
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"n": "vr_b3oVWMRwGQknVn8EVKmsnKgQlFN6h5aRkEkvVw4x50w-C9pMxK4D9yyxo1ijiBTQ4A2ePr-VpEr3n1Yj0Kvz5JqfpQPlLC1pSmw_cJp_gLRMjlhyGCFV4zWa3XXrfEcpJrgd-Iz5e-rKIMPq1F0t6Luq-yj9EZSDi09QBdsj8ZFc47HSDzVUotVPuzkDgJlPYODfnd_7dz9H8rTR8Lu-uv-RCU308UgAphNNPlSISjUIhKU9j-an9kAtmOpMElqF4ChWQXFhxhn8DFHnQ_NhP80ugK3BT1hnM5KwlqocG90B0CDbBDA2JcdXCRZ8o_EsRZP43_jA49tU6Xc_8Vw"
},
{
"e": "AQAB",
"n": "q44fdZly8llGEwROShl8cdTz9UHX4q-rqYg4xtLgMeMw4vsIBd2OojPMBa49HVLqEdDbOuAT4wsfcYCESGBvkPsGpWIV9XrZYoKfNhh-NFxgFTqS-RafneYe5_613G6q3ZCOk3kMcpqxej7pJ29RywCB5afQPddnF8pZa9_Bg_5TCdcLG5y84nV0SLhXfZ0aAMMPVt405VJCVcilGwvPpddmHfq2m37Q4gBilodjXnafQ6iysCUdI9qTdT3eW4hziYUAyF6nKtBcmzwdAUEG_yGxJJUFHIftWT_cljV4pzAjszkOiMOaOUGuRDvgn_8qTRo2xkwuQ5yoK7HepYTzJQ",
"alg": "RS256",
"kid": "98dc55c8b209363a2451774bce5c42718d13cb7d",
"use": "sig",
"kty": "RSA"
}
]
}
なお、セキュリティ向上のためIDプロバイダは定期的に鍵をローテーションします。そのためクライアントはkidを見て正しい鍵を選び、必要に応じて新しい鍵を取得して検証する必要があります。
署名検証は何をしているのか(ChatGPTより)
公開鍵を使って受け取ったJWTのヘッダーとペイロードから署名を再計算し、JWTに付与されている署名と比較します。この2つが一致すれば、以下が保証されます。
- JWTが正当な発行者によって作られたこと
- ヘッダーやペイロードの内容が改ざんされていないこと
つまり署名検証は、「このデータは正しい人が作ったものか?途中で変えられていないか?」を確認しています。
まとめ
今回は認可のフレームワークであるOAuthに続いて認証について学習した内容をまとめました。
ここまでは書籍を読んでひたすら知識を詰め込みましたが、次回は実際にログイン機能を実装しながら学習した内容を確かめていきたいと思います。