9.9. OAuth 2.0¶
9.9.1. Overview¶
本節では、OAuth 2.0の概要とSpringプロジェクトの一つであるSpring Securityを使用してOAuth 2.0の仕様に沿った認可制御機能を実装する方法について説明する。なお、本節はSpring Security (org.springframework.security)が提供するOAuthに関する説明となる。Spring Security OAuth (org.springframework.security.oauth)に関する説明はOAuth(org.springframework.security.oauth)を参照されたい。
Tip
Spring Security が提供するOAuth 2.0のリファレンス
Spring Security が提供するOAuth 2.0は、本ガイドラインで紹介していない機能も提供している。Spring Security が提供するOAuth 2.0について詳しく知りたい場合は、OAuth2を参照されたい。
9.9.1.1. OAuth 2.0とは¶
OAuth 2.0とは、サードパーティ製アプリケーションがHTTPサービスを利用する際に、サーバ上の保護されたリソースに対するアクセス範囲の指定を可能にするための認可フレームワークのことである。
OAuth 2.0はRFCとして仕様化されており、関連する複数の技術仕様から構成されている。
以下にOAuth 2.0の主要な仕様を示す。
RFC | 概要 | 説明 |
---|---|---|
RFC 6749
|
用語や認可方式などの、OAuth 2.0としてのもっとも基本的な内容が記載されている技術仕様。
|
|
RFC 6750
|
RFC 6749に記載されている認可制御を実現する場合に利用する、「署名なしアクセストークン」(以降、アクセストークンと表す)のサーバ間の受け渡し方法に関する技術仕様。
アクセストークンについては後述する。
|
|
RFC 6819
|
OAuth 2.0を使用するうえで考慮が必要となるセキュリティ要件に関する技術仕様。
本ガイドラインでは検討項目の具体的な説明は割愛する。
|
|
RFC 7519
|
署名が可能なJSONを含んだトークンであるJSON Web Token (JWT)に関する技術仕様。
|
|
RFC 7523
|
RFC 6749に記載されている認可制御の実現する場合に利用するアクセストークンとして、RFC 6819で定められているJWTを利用する方法に関する技術仕様。
|
|
RFC 7009
|
トークンの無効化を行う追加エンドポイントに関する技術仕様。
|
従来のクライアントサーバ型の認証モデルでは、サードパーティ製アプリケーションはHTTPサービスの保護されたリソースにアクセスするために、ユーザの認証情報(ユーザ名とパスワードなど)を利用して認証を行う。
つまり、ユーザは、サードパーティ製アプリケーションにリソースへのアクセス権を与えるために自身の認証情報をサードパーティと共有する必要があるが、これはサードパーティ製アプリケーションに不具合や悪意のある操作などが存在した場合に、ユーザの意図しないアクセスや情報漏洩等のリスクにつながる。
これに対し、OAuth 2.0ではHTTPサービスとの認証はユーザが直接行い、サードパーティ製アプリケーションには「アクセストークン」と呼ばれる認証済みリクエストを行うための情報を払い出すことで、サードパーティに認証情報を共有することなくリソースへアクセスすることが可能となる。
また、アクセストークン発行時にリソースに対するアクセス範囲(スコープ)を指定可能とすることで従来のクライアントサーバ型の認証モデルと比較してより柔軟なアクセス制御を実現している。
9.9.1.2. OAuth 2.0のアーキテクチャ¶
9.9.1.2.1. ロール¶
OAuth 2.0ではロールとして以下の4つを定義している。
ロール名 | 説明 |
---|---|
リソースオーナ
|
保護されたリソースへのアクセスを許可するロール。人(エンドユーザ)など。
|
リソースサーバ
|
保護されたリソースを提供するサーバ。
|
認可サーバ
|
リソースオーナの認証と、アクセストークン(クライアントがリソースサーバにアクセスするときに必要な情報)の発行を行うサーバ。
|
クライアント
|
リソースオーナの認可を得て、リソースオーナの代理として保護されたリソースに対してリクエストを行うロール。Webアプリケーションなど。クライアント情報は事前に認可サーバに登録され、認可サーバ内で一意な情報であるクライアントIDにより管理される。
OAuth 2.0ではクライアントクレデンシャル(クライアントの認証情報)の機密性を維持できる能力に基づき、クライアントタイプとして以下の2つを定義している。
(1) コンフィデンシャル
クライアントクレデンシャルの機密性を維持することができるクライアント。
(2) パブリック
リソースオーナのデバイス上で実行されるクライアントのように、クライアントクレデンシャルの機密性を維持することができず、かつ他の手段を用いたセキュアなクライアント認証が行えないクライアント。
また、OAuth 2.0ではクライアントとして以下のような例を考慮して設計されている。
(1) コンフィデンシャル
(2) パブリック
|
Note
ユーザエージェントは、リソースオーナが使用するWebブラウザ等を指す。本ガイドラインでは、エンドユーザの操作が発生する箇所を明確にするため、リソースオーナ(エンドユーザ)とユーザエージェントを別のものとして解説する。
ガイドラインでリソースオーナと明示している場合に、エンドユーザの操作が発生する。
9.9.1.2.2. スコープ¶
OAuth 2.0では保護されたリソースに対するアクセスを制御する方法としてスコープという概念を使用している。
認可サーバはクライアントからの要求に対し、認可サーバのポリシーまたはリソースオーナの指示に基づいてアクセストークンにスコープを含め、保護されたリソースに対するアクセス権(読み込み権限、書き込み権限など)を指定することが出来る。
9.9.1.2.3. プロトコルフロー¶
OAuth 2.0では、以下のような流れでリソースへのアクセスを行う。
項番 | 説明 |
---|---|
(1)
|
リソースオーナに対して認可を要求する。上の図ではクライアントがリソースオーナに直接要求を行っているが、認可サーバを経由して行うほうが望ましい。
後述するグラントタイプの中では認可コードグラントとインプリシットグラントが認可サーバを経由してリソースオーナに要求を行うフローになっている。
|
(2)
|
クライアントはリソースオーナからの認可を表すクレデンシャルとして認可グラント(後述)を受け取る。
|
(3)
|
クライアントは、認可サーバに対して自身の認証情報とリソースオーナが与えた認可グラントを提示することで、アクセストークンを要求する。
|
(4)
|
認可サーバはクライアントを認証し、認可グラントの正当性を確認する。認可グラントが正当な場合、アクセストークンを発行する。
|
(5)
|
クライアントはリソースサーバの保護されたリソースへリクエストを行い、発行されたアクセストークンにより認証する。
|
(6)
|
リソースサーバはアクセストークンの正当性を確認し、正当な場合、リクエストを受け入れリソースを応答する。
|
Note
OAuth 1.0で不評だった署名とトークン交換の複雑な仕組みを簡略化するために、OAuth 2.0ではアクセストークンを扱うリクエストはHTTPS通信で行うことを必須としている。(HTTPS通信を使用することでアクセストークンの盗聴を防止する)
9.9.1.2.4. 認可グラント¶
認可グラントは、リソースオーナからの認可を表し、クライアントがアクセストークンを取得する際に用いられる。OAuth 2.0では、グラントタイプとして以下の4つを定義しているが、クレデンシャル項目を追加するなどの独自拡張を行うこともできる。
グラントタイプ | 説明 |
---|---|
認可コードグラント
|
認可コードグラントのフローでは、認可サーバがクライアントとリソースオーナの仲介となって認可コードをクライアントへ発行し、クライアントが認可コードを認可サーバに渡すことでアクセストークンを発行する。
認可サーバが発行した認可コードを使用してアクセストークンを発行するため、クライアントへリソースオーナのクレデンシャルを共有する必要がない。
認可コードグラントはWebアプリケーションのように、コンフィデンシャルなクライアントがOAuth 2.0を利用する際に使用する。
|
インプリシットグラント
|
インプリシットグラントのフローでは、認可コードグラントと同様に認可サーバが仲介するが、認可コードの代わりに直接アクセストークンを発行する。
これにより応答性、効率性が高いため、スクリプト言語を使用してブラウザ上で実行されるクライアントに適している。
しかし、アクセストークンがURL中にエンコードされるため、リソースオーナや同一デバイス上の他のアプリケーションに漏えいする可能性があるほか、クライアントの認証を行わないことから、他のクライアントに対して発行されたアクセストークンを不正に用いた成りすまし攻撃のリスクがある。
セキュリティ上のリスクがあるため、応答性、効率性が求められるパブリックなクライアントでのみ使用すること。
|
リソースオーナパスワードクレデンシャルグラント
|
リソースオーナパスワードクレデンシャルグラントのフローでは、クライアントがリソースオーナの認証情報を認可グラントとして使用して、直接アクセストークンを発行する。
クライアントへリソースオーナのクレデンシャルを共有する必要があるため、クライアントの信頼性が低い場合、クレデンシャルの不正利用や漏洩のリスクがある。
リソースオーナパスワードクレデンシャルグラントはリソースオーナとクライアントの間で高い信頼があり、かつ他のグラントタイプが利用できない場合にのみ使用すること。
|
クライアントクレデンシャルグラント
|
クライアントクレデンシャルグラントのフローでは、クライアントの認証情報を認可グラントとして使用して、直接アクセストークンを発行する。
クライアントがリソースオーナであるような場合に使用する。
|
Warning
OAuth 2.0における認可グラントで解説した通り、認可コードグラント以外のグラントタイプには、セキュリティ上のリスクや、使用上の制約がある。そのため、認可コードグラントの利用を優先して検討されたい。
9.9.1.2.4.1. 認可コードグラント¶
認可コードグラントのフローを以下に示す。
項番 | 説明 |
---|---|
(1)
|
リソースオーナは、ユーザエージェント(Webブラウザなど)を介してクライアントが提供するリソースサーバの保護されたリソースにアクセスする。
クライアントはリソースオーナから認可の取得を行うために、リソースオーナが操作するユーザエージェントを認可サーバの認可エンドポイントにリダイレクトさせる。
このとき、クライアントは自身を識別するためのクライアントIDと、オプションとしてリソースに要求するスコープ、認可サーバが認可処理後にユーザエージェントを戻すリダイレクトURI、stateをリクエストパラメータに含める。
stateはユーザエージェントに紐付くランダムな値であり、一連のフローが同じユーザエージェントで実行されたことを保証するために利用される(CSRF対策)。
|
(2)
|
ユーザエージェントは、クライアントに指示された認可サーバの認可エンドポイントにアクセスする。
認可サーバはユーザエージェント経由でリソースオーナを認証し、リクエストパラメータのクライアントID、スコープ、リダイレクトURIを元に、自身に登録済みのクライアント情報と比較しパラメータの正当性確認を行う。
確認完了後、アクセス要求の許可/拒否をリソースオーナに問い合わせる。
|
(3)
|
リソースオーナはアクセス要求の許可/拒否を認可サーバに送信する。
リソースオーナがアクセスを許可した場合、認可サーバは、リクエストパラメータに含まれるリダイレクトURIを用いて、ユーザエージェントをクライアントにリダイレクトさせる指示を出す。
その際、認可コードをリダイレクトURIのリクエストパラメータとして付与する。
|
(4)
|
ユーザエージェントは認可コードが付与されたリダイレクトURIにアクセスする。
クライアントの処理が完了するとリソースオーナにレスポンスを返却する。
|
(5)
|
クライアントはアクセストークンを要求するために、認可コードを認可サーバのトークンエンドポイントに送信する。
認可サーバのトークンエンドポイントはクライアントの認証と認可コードの正当性の検証を行い、正当である場合アクセストークンと任意でリフレッシュトークンを発行する。
リフレッシュトークンはアクセストークンが無効化された、または期限切れの際に新しいアクセストークンを発行するために使用される。
|
9.9.1.2.4.2. インプリシットグラント¶
インプリシットグラントのフローを以下に示す。
項番 | 説明 |
---|---|
(1)
|
リソースオーナは、ユーザエージェントを介してクライアントが提供するリソースサーバの保護されたリソースが必要なページにアクセスする。
クライアントはリソースオーナから認可の取得とアクセストークンの発行を行うために、リソースオーナのユーザエージェントを認可サーバの認可エンドポイントにアクセスさせる。
このとき、クライアントは自身を識別するためのクライアントIDと、オプションとしてリソースに要求するスコープ、認可サーバが認可処理後にユーザエージェントを戻すリダイレクトURI、stateをリクエストパラメータに含める。
stateはユーザエージェントに紐付くランダムな値であり、一連のフローが同じユーザエージェントで実行されたことを保証するために利用される(CSRF対策)。
|
(2)
|
ユーザエージェントは、クライアントに指示された認可サーバの認可エンドポイントにアクセスする。
認可サーバはユーザエージェント経由でリソースオーナを認証し、リクエストパラメータのクライアントID、スコープ、リダイレクトURIを元に、自身に登録済みのクライアント情報と比較しパラメータの正当性確認を行う。
確認完了後、アクセス要求の許可/拒否をリソースオーナに問い合わせる。
|
(3)
|
リソースオーナはアクセス要求の許可/拒否を認可サーバに送信する。
リソースオーナがアクセスを許可した場合、認可サーバの認可エンドポイントはリクエストパラメータのリダイレクトURIを用いてユーザエージェントをクライアントリソースにリダイレクトさせる指示を出し、アクセストークンをリダイレクトURIのURLフラグメントに付与する。
ここで「クライアントリソース」とは、クライアントアプリケーションとは別にWebサーバ等にホストしておいた静的リソースを指す。
|
(4)
|
ユーザエージェントはリダイレクトの指示に従い、クライアントリソースにリクエストを送信する。このとき、URLフラグメントの情報をローカルで保持し、リダイレクトの際にはURLフラグメントを送信しない。
クライアントリソースにアクセスすると、Webページ(通常は埋め込みスクリプトを含むHTMLドキュメント)が返却される。
ユーザエージェントはWebページに含まれるスクリプトを実行し、ローカルで保持していたURLフラグメントからアクセストークンを抽出する。
|
(5)
|
ユーザエージェントはアクセストークンをクライアントに渡す。
|
9.9.1.2.4.3. リソースオーナパスワードクレデンシャルグラント¶
リソースオーナパスワードクレデンシャルグラントのフローを以下に示す。
項番 | 説明 |
---|---|
(1)
|
リソースオーナがクライアントにクレデンシャル(ユーザ名、パスワード)を提供する。
|
(2)
|
クライアントはアクセストークンを要求するために、認可サーバのトークンエンドポイントにアクセスする。
このとき、クライアントはリソースオーナから指定されたクレデンシャルとリソースに要求するスコープをリクエストパラメータに含める。
|
(3)
|
認可サーバのトークンエンドポイントはクライアントを認証し、リソースオーナのクレデンシャルを検証する。正当である場合アクセストークンを発行する。
|
9.9.1.2.4.4. クライアントクレデンシャルグラント¶
クライアントクレデンシャルグラントのフローを以下に示す。
項番 | 説明 |
---|---|
(1)
|
クライアントはアクセストークンを要求するために、認可サーバのトークンエンドポイントにアクセスする。
このとき、クライアントはクライアント自身のクレデンシャルを含めてアクセストークンを要求する。
|
(2)
|
認可サーバのトークンエンドポイントはクライアントを認証し、認証に成功した場合アクセストークンを発行する。
|
9.9.1.2.5. アクセストークンのライフサイクル¶
アクセストークンはクライアントが提示する認可グラントの正当性を認可サーバが確認することで発行される。発行されたアクセストークンは、認可サーバのポリシーまたはリソースオーナの指示に基づいたスコープが与えられ、保護されたリソースに対するアクセス権を保持する。アクセストークンは発行時に有効期限が設定され、有効期限切れとなると保護されたリソースに対するアクセス権を失効される。
アクセストークンの発行から失効までの流れは以下のようになる。
項番 | 説明 |
---|---|
(1)
|
クライアントが認可グラントを提示し、アクセストークンを要求する。
|
(2)
|
認可サーバはクライアントが提示した認可グラントを確認し、アクセストークンを発行する。
|
(3)
|
クライアントはアクセストークンを提示し、リソースサーバの保護されたリソースを要求する。
|
(4)
|
リソースサーバはクライアントが提示したアクセストークンの正当性を検証し、正当であればリソースサーバの保護されたリソースに対して処理を行う。
|
(5)
|
クライアントはアクセストークン(有効期限切れ)を提示し、リソースサーバの保護されたリソースを要求する。
|
(6)
|
リソースサーバはクライアントが提示したアクセストークンの正当性を検証し、アクセストークンの有効期限が切れている場合はエラーを返却する。
|
リフレッシュトークンによるアクセストークンの再発行の流れは以下のようになる。
項番 | 説明 |
---|---|
(1)
|
クライアントが認可グラントを提示し、アクセストークンを要求する。
|
(2)
|
認可サーバはクライアントが提示した認可グラントを確認し、アクセストークンとリフレッシュトークンを発行する。
|
(3)
|
クライアントはアクセストークンを提示し、リソースサーバの保護されたリソースを要求する。
|
(4)
|
リソースサーバはクライアントが提示したアクセストークンの正当性を検証し、正当であればリソースサーバの保護されたリソースに対して処理を行う。
|
(5)
|
クライアントはアクセストークン(有効期限切れ)を提示し、リソースサーバの保護されたリソースを要求する。
|
(6)
|
リソースサーバはクライアントが提示したアクセストークンの正当性を検証し、アクセストークンの有効期限が切れている場合はエラーを返却する。
|
(7)
|
リソースサーバよりアクセストークンの有効期限切れエラーが返却された場合、クライアントはリフレッシュトークンを提示することで新しいアクセストークンを要求する。
|
(8)
|
認可サーバはクライアントが提示したリフレッシュトークンの正当性を検証し、正当であればアクセストークンとオプションでリフレッシュトークンを発行する。
|
リフレッシュトークンの有効期限が期限切れとなった場合は認可サーバへ認可グラントの再提示を行う。
項番 | 説明 |
---|---|
(1)
|
クライアントが有効期限切れのアクセストークンを提示し、リソースサーバよりアクセストークンの有効期限切れエラーが返却された場合、クライアントはリフレッシュトークン(有効期限切れ)を提示することで新しいアクセストークンを要求する。
|
(2)
|
認可サーバはクライアントが提示したリフレッシュトークンの正当性を検証し、リフレッシュトークンの有効期限が切れている場合はエラーを返却する。
|
(3)
|
認可サーバよりリフレッシュトークンの有効期限切れエラーが返却された場合、クライアントは認可グラントを再提示し、アクセストークンを要求する。
|
(4)
|
認可サーバはクライアントが提示した認可グラントを確認し、アクセストークンとリフレッシュトークンを発行する。
|
9.9.1.3. Spring Securityより提供されるOAuth2のアーキテクチャ¶
Spring Security を使用してリソースサーバ、クライアントを構築した場合、以下のような流れで処理が行われる。
項番 | 説明 |
---|---|
(1)
|
リソースオーナはユーザエージェントを介してクライアントへアクセスする。
|
(2)
|
クライアントは
OAuth2AuthorizedClientManager を介してOAuth2AuthorizedClient を要求する。 |
(3)
|
要求された
OAuth2AuthorizedClient が認可サーバで許可されていない場合、ユーザエージェントへ認可サーバの認可エンドポイントへリダイレクトさせるよう指示する。 |
(4)
|
ユーザエージェントは認可サーバの認可エンドポイントへリダイレクトする。
|
(5)
|
認可エンドポイントはリソースオーナへ認可を問い合わせる画面を表示した後に、リソースオーナからの認可リクエストを受け取り認可コードを発行する。
発行した認可コードは、リダイレクトURIのリクエストパラメータとしてユーザエージェント経由でクライアントに渡される。
|
(6)
|
クライアントは受け取った認可コードを内部のコンテキストに保持し、リクエストを再度処理する。
|
(7)
|
クライアントは
OAuth2AuthorizedClientManager を介してOAuth2AuthorizedClient を要求する。 |
(8)
|
要求された
OAuth2AuthorizedClient が認可サーバで許可されている場合、クライアントは認可コードを使用してトークンエンドポイントからアクセストークンを取得する。すでにアクセストークンが取得済みかつアクセストークンの有効期限が切れている場合は、トークンエンドポイントに対しアクセストークンの再払い出しを要求する。
|
(9)
|
クライアントは
RestTemplate のヘッダに(8)で取得したアクセストークンを設定し、リソースサーバにアクセスする。 |
(10)
|
リソースサーバはアクセストークンを受け取ると、アクセストークンの検証を行う。
|
(11)
|
リソースサーバはアクセストークンの検証に成功した場合、クライアントからのリクエストに応じたリソースを返却する。
|
9.9.1.3.1. 認可サーバ¶
認可サーバはクライアント情報(どのクライアントに、どのリソースに対するどのスコープの認可を与えるかの情報)に基づいて、リソースにどのスコープでのアクセスを認可するか、アクセストークンを発行してよいかの検証を行う。
9.9.1.3.2. リソースサーバ¶
リソースサーバでは、アクセストークン自体の妥当性とアクセストークンが保持するスコープ内のリソースへのアクセスであることを検証する機能を提供する。
Spring Securityでは、アクセストークンが付与されていないリクエストを受信した際にWWW-Authenticate
ヘッダーをクライアントへ送信し、アクセストークンでの認証が必要なことを伝える。アクセストークンが送信されると、Spring Securityはアクセストークンの検証を行い、問題がない場合にアプリケーションロジックを実行する。
9.9.1.3.2.1. アクセストークン受信前¶
項番 | 説明 |
---|---|
(1)
|
クライアントからアクセストークンを付与していないリクエストを受信する。
|
(2)
|
リソースサーバはアクセストークンが付与されていないリクエストを受信すると
FilterSecurityInterceptor でAccessDeniedException をスローしアクセスを拒否する。 |
(3)
|
ExceptionTranslationFilter でアクセスを拒否したことを検知し、クライアントへWWW-Authenticate ヘッダーを送信する。クライアントは
WWW-Authenticate が返却されることでアクセストークンでの認証が必要である旨を検知する。 |
9.9.1.3.2.2. アクセストークン受信後¶
アクセストークン受信後のフローを以下に示す。
項番 | 説明 |
---|---|
(1)
|
クライアントからアクセストークンを付与したリクエストを受信する。
|
(2)
|
HttpServletRequest からBearerTokenAuthenticationToken を抽出し、AuthenticationManager へ値を渡す。 |
(3)
|
AuthenticationManager は受け取ったBearerTokenAuthenticationToken の検証を行う。
|
(4)
|
(3)の検証結果より、クライアントから受け取ったアクセストークンでの認証が成功した場合、認証情報を
SecurityContextHolder に保存する。 |
(4)’
|
(3)の検証結果より、クライアントから受け取ったアクセストークンでの認証が失敗した場合、
SecurityContextHolder の情報を削除し、クライアントへWWW-Authenticate ヘッダーを送信する。 |
(5)
|
クライアントからリクエストされたスコープに基づいたリソースに対する処理を行う。
|
9.9.1.3.3. クライアント¶
クライアントでは、リソースオーナからの認可を取得するために認可サーバへリダイレクトさせる機能と、認可サーバからアクセストークンを取得してリソースサーバへアクセスする機能を提供する。
Spring Securityは、アクセストークンを取得してリソースサーバへアクセスするためにOAuth2AuthorizedClient
、OAuth2AuthorizationRequestRedirectFilter
、OAuth2AuthorizedClientRepository
、OAuth2AuthorizedClientManager
、OAuth2AuthorizedClientProvider
を提供している。
OAuth2AuthorizedClientManager
でOAuth2AuthorizedClient
を管理し、OAuth2AuthorizedClient
が認可前の場合にClientAuthorizationRequiredException
を発生させ、OAuth2AuthorizationRequestRedirectFilter
で例外をハンドリングして認可サーバへリダイレクトさせることが可能となる。
また、OAuth2AuthorizedClientManager
では認可されたOAuth2AuthorizedClient
をOAuth2AuthorizedClientRepository
で管理する機能を提供する。これにより、複数のリクエスト間でアクセストークンを共有することが可能となる。
9.9.1.3.3.1. クライアント認可処理¶
クライアントの認可処理のフローを以下に示す。
項番 | 説明 |
---|---|
(1)
|
ユーザエージェントがクライアントのServiceの呼び出しが行われるよう、Security Filterの処理を実施後にControllerへアクセスする。
|
(2)
|
Serviceより
OAuth2AuthorizedClientManager を呼び出し、OAuth2AuthorizedClient を要求する。 |
(3)
|
OAuth2AuthorizedClient が認可前の場合、OAuth2AuthorizedClientProvider はClientAuthorizationRequiredException を発生させ、OAuth2AuthorizationRequestRedirectFilter でハンドリングさせることで、ユーザエージェントに対し認可サーバへのリダイレクトを促す。この時、(1)で受け付けた
HttpServletRequest はHTTPセッションに保存される。 |
(4)
|
ユーザエージェントから認可サーバの認可エンドポイントに対しリダイレクトする。
|
(5)
|
認可サーバで、リソースオーナーの認証、クライアントの認可を実施後、ユーザエージェントに対しクライアントへのリダイレクト処理を促す。その際に、認可コードをパラメータとして設定する。
|
(6)
|
ユーザエージェントは認可処理後のリダイレクトURIに対しリダイレクトを行う。
リダイレクトされたクライアントは、認可コードを内部の情報に保持した後、(3)でHTTPセッションに保存した
HttpServletRequest を取り出し再処理を行う。 |
(7)
|
Serviceより
OAuth2AuthorizedClientManager を呼び出し、OAuth2AuthorizedClient を要求する。 |
(8)
|
(6)で内部の情報に保持した認可コードを使用し、認可サーバのトークンエンドポイントに対しアクセストークンを要求し、取得したアクセストークン及びリフレッシュトークンを
OAuth2AuthorizedClient に設定する。 |
(9)
|
(8)で取得したアクセストークンをヘッダに設定し、リソースサーバへアクセスする。
|
9.9.1.3.3.2. クライアント認可後¶
クライアントが認可後のフローを以下に示す。
項番 | 説明 |
---|---|
(1)
|
ユーザエージェントがクライアントのServiceの呼び出しが行われるよう、Security Filterの処理を実施後にControllerへアクセスする。
|
(2)
|
Serviceより
OAuth2AuthorizedClientManager を呼び出し、OAuth2AuthorizedClient を要求する。 |
(3)
|
認可済みの
OAuth2AuthorizedClient に格納されたアクセストークンを取得する。アクセストークンの有効期限が切れている場合、リフレッシュトークンを使用し、認可サーバのトークンエンドポイントに対しアクセストークンのリフレッシュを要求する。リフレッシュされたアクセストークン及びリフレッシュトークンをOAuth2AuthorizedClient に設定する。リフレッシュトークンの有効期限も切れている場合は
OAuth2AuthorizationException がスローされる。例外がスローされた場合、OAuth2AuthorizedClientRepository に保存しているOAuth2AuthorizedClient は削除される。 |
(4)
|
(3)で取得したアクセストークンをヘッダに設定し、リソースサーバへアクセスする。
|
9.9.2. How to use¶
Spring Security が提供するOAuth2を使用するために必要となるBean定義例や実装方法について説明する。
9.9.2.1. How to Useの構成¶
認可グラントで記載した通り、OAuth 2.0ではグラントタイプにより認可サーバ、クライアント間のフローが異なる。そのため、アプリケーションがサポートするグラントタイプに沿った実装を行う必要がある。
本ガイドラインでは、認可コードグラントについてリソースサーバ、クライアントの実装方法の解説を行う。
9.9.2.2. 認可コードグラントの実装¶
認可コードグラントを利用したリソースサーバ、クライアントの実装方法について説明する。
9.9.2.2.1. リソースサーバの実装¶
リソースサーバの実装方法について説明する。
リソースサーバでは、アクセストークンの検証とリソースに対しての認可制御をSpring Securityの機能を使用して提供する。
ここではTODOリソースのREST APIに対して認可制御を実現する方法を説明する。
9.9.2.2.1.1. 依存ライブラリ設定¶
アクセストークンによる認証を行えるようにするため、pom.xml
にライブラリを追加する。マルチプロジェクト構成の場合は、domainプロジェクトのpom.xml
に追加する。
pomt.xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
Note
上記設定例のspring-security-oauth2-resource-server
とspring-security-oauth2-jose
は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xml
でのバージョンの指定は不要である。また、本ガイドラインではJWT認証を用いるため、spring-security-jwt
を設定している。spring-security-jwt
は親プロジェクトで管理していないため、個別に設定されたい。
9.9.2.2.1.2. 設定ファイルの作成(リソースサーバ)¶
リソースサーバを実装する際には新たにOAuth 2.0用のBean定義ファイルを作成する。
ここでは oauth2-resource.xml
とする。
oauth2-resource.xml
には以下の設定を追加する。
oauth2-resource.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
">
<sec:http pattern="/api/v1/todos/**"> <!-- (1) -->
<!-- omitted -->
<sec:oauth2-resource-server>
<sec:jwt jwk-set-uri="https://idp.example.org/.well-known/jwks.json" /> <!-- (2) -->
</sec:oauth2-resource-server>
</sec:http>
</beans>
項番 | 説明 |
---|---|
(1)
|
pattern 属性には認可制御の対象とするパスのパターンを指定する。 |
(2)
|
jwk-set-uri を指定することでリソースサーバーは自動的にJWTエンコードされたアクセストークンを検証するように構成される。設定するJWK Set uri は使用する認可サーバのアーキテクチャ仕様を確認されたい。
|
作成したoauth2-resource.xml
を読み込むようにweb.xml
に設定を追加する。
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:META-INF/spring/applicationContext.xml
classpath*:META-INF/spring/oauth2-resource.xml <!-- (1) -->
classpath*:META-INF/spring/spring-security.xml
</param-value>
</context-param>
項番 | 説明 |
---|---|
(1)
|
oauth2-resource.xml で設定したパスのパターンを内包するようなパスがspring-security.xml にアクセス制御対象として設定されている場合を考慮し、先にoauth2-resource.xml を読み込むようにする。 |
9.9.2.2.1.3. リソースにアクセス可能なスコープの設定¶
リソースごとにアクセス可能なスコープを定義するために、OAuth 2.0用のBean定義ファイルにスコープを追加する。
実装例は以下の通りである。
oauth2-resource.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
">
<sec:http pattern="/api/v1/todos/**">
<!-- omitted -->
<sec:intercept-url pattern="/api/v1/todos/**" method="GET" access="hasAuthority('SCOPE_READ')" /> <!-- (1) -->
<sec:intercept-url pattern="/api/v1/todos/**" method="POST" access="hasAuthority('SCOPE_CREATE')" /> <!-- (1) -->
<sec:intercept-url pattern="/api/v1/todos/**" method="PUT" access="hasAuthority('SCOPE_UPDATE')" /> <!-- (1) -->
<sec:intercept-url pattern="/api/v1/todos/**" method="DELETE" access="hasAuthority('SCOPE_DELETE')" /> <!-- (1) -->
<sec:oauth2-resource-server>
<sec:jwt jwk-set-uri="https://idp.example.org/.well-known/jwks.json" />
</sec:oauth2-resource-server>
</sec:http>
</beans>
項番 | 説明 |
---|---|
(1)
|
intercept-url を使用してリソースに対してスコープによるアクセスポリシーを定義する。
|
また、Bean定義でスコープを指定せずに、以下の様にメソッドアノテーションを使用することでもスコープによる保護が可能となる。メソッドアノテーションの詳細についてはメソッドへの認可を参照されたい。
@GetMapping("/")
@PreAuthorize("hasAuthority('SCOPE_READ')") // (1)
public List<Message> getMessages(...) {}
項番 | 説明 |
---|---|
(1)
|
PreAuthorize を使用してリソースに対してスコープによるアクセスポリシーを定義する。 |
9.9.2.2.2. クライアントの実装¶
クライアントの実装方法について説明する。
クライアントでは、グラントタイプやスコープなどのアプリケーション用件に沿ったパラメータを定義することで、OAuth 2.0機能を使用したリソースへのアクセスが可能となる。
9.9.2.2.2.1. 依存ライブラリ設定¶
Spring Security のOAuth2.0クライアント機能を使用するため、pom.xml
にライブラリを追加する。マルチプロジェクト構成の場合は、domainプロジェクトのpom.xml
に追加する。
pom.xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
Note
上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xml
でのバージョンの指定は不要である。
9.9.2.2.2.2. 設定ファイルの作成(クライアント)¶
クライアントを実装する際には新たにOAuth 2.0用のBean定義ファイルを作成する。
ここではoauth2-client.xml
とする。
oauth2-client.xml
には以下の設定を追加する。
oauth2-client.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<sec:http pattern="/api/v1/todos/**">
<!-- omitted -->
<sec:oauth2-client /> <!-- (1) -->
</sec:http>
</beans>
項番 | 説明 |
---|---|
(1)
|
<sec:http>要素の子要素として<sec:oauth2-client>要素を指定する。
<sec:oauth2-client>要素を指定すると、OAuth 2.0 クライアント機能が適用される。
|
Note
本ガイドラインではOAuth 2.0 クライアント機能をデフォルトの状態で使用している。OAuth2AuthorizedClient
を管理するためのClientRegistrationRepository
及びOAuth2AuthorizedClientRepository
が自動的にBean定義されるが、要件に応じ適切にカスタマイズされたい。
カスタマイズ可能な構成オプションについてはOAuth 2.0 Clientを参照されたい。
作成したoauth2-client.xml
を読み込むようにweb.xml
に設定を追加する。
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:META-INF/spring/applicationContext.xml
classpath*:META-INF/spring/oauth2-client.xml <!-- (1) -->
classpath*:META-INF/spring/spring-security.xml
</param-value>
</context-param>
項番 | 説明 |
---|---|
(1)
|
oauth2-client.xml で設定したパスのパターンを内包するようなパスがspring-security.xml にアクセス制御対象として設定されている場合を考慮し、先にoauth2-client.xml を読み込むようにする。 |
9.9.2.2.2.3. 例外ハンドリングの除外設定¶
ClientAuthorizationRequiredException
を発生させ、OAuth2AuthorizationRequestRedirectFilter
でハンドリングすることで認可サーバが提供するリソースオーナの認可を取得するためのページへリダイレクトさせている。OAuth2AuthorizationRequestRedirectFilter
はSecurity Filterとして提供されており、ブランクプロジェクトで予め設定しているSystemExceptionResolver
が先にClientAuthorizationRequiredException
をハンドリングしてしまうと期待した動作にならない。そのため、spring-mvc.xml
の設定を変更し、SystemExceptionResolver
がClientAuthorizationRequiredException
をハンドリングしないようにする必要がある。SystemExceptionResolver
の詳しい解説については例外ハンドリングを参照されたい。spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
<!-- omitted -->
<property name="excludedExceptions">
<array>
<!-- (1) -->
<value>org.springframework.security.oauth2.client.ClientAuthorizationRequiredException</value>
</array>
</property>
</bean>
項番 | 説明 |
---|---|
(1)
|
ClientAuthorizationRequiredException をSystemExceptionResolver のハンドリング対象から除外する。 |
9.9.2.2.2.4. クライアントの登録¶
ClientRegistration
を使用し、認可サーバに登録されたクライアントを定義する。ClientRegistrationRepository
に保持される。oauth2-client.xml
<sec:client-registrations>
<sec:client-registration
registration-id="okta-client-id"
client-id="readClient"
client-secret="okta-client-secret"
authorization-grant-type="authorization_code"
redirect-uri="{baseUrl}/authorized/okta"
scope="READ,CREATE"
provider-id="okta" /> <!-- (1)~(7) -->
<sec:provider provider-id="okta"
authorization-uri="https://dev-1234.oktapreview.com/oauth2/v1/authorize"
token-uri="https://dev-1234.oktapreview.com/oauth2/v1/token"
/> <!-- (8)~(9) -->
</sec:client-registrations>
項番 | 説明 |
---|---|
(1)
|
registration-id 属性に、ClientRegistrationを一意に識別するBean IDを設定する。 |
(2)
|
client-id 属性に、認可サーバに登録されたクライアントIDを設定する。 |
(3)
|
client-secret 属性に、認可サーバに登録されたクライアントの認可に用いるパスワードを設定する。 |
(4)
|
authorization-grant-type 属性に、グラントタイプを設定する。認可コードグラントの場合authorization_code を指定する。 |
(5)
|
redirect-uri 属性に、認可サーバが認可コード発行後にユーザエージェントからクライアントへリダイレクトさせるためのリダイレクトURIを設定する。このURIへリダイレクト後、Spring SecurityはClientAuthorizationRequiredException を発生させたリクエストの再処理を行う。リダイレクトURIは認可サーバに登録されているリダイレクトURIと一致させる必要がある。詳しくはRFC6749#section-3.1.2を参照されたい。
|
(6)
|
scope 属性には、認可リクエストとしてクライアントにリクエストするスコープをカンマ区切りで設定する。
|
(7)
|
provider-id 属性には、エンドポイントを設定したプロバイダのIDを設定する。 |
(8)
|
authorization-uri 属性には、認可サーバーの認可エンドポイントURIを設定する。クライアントが認可前の場合は当認可エンドポイントURIに対しリクエストを送信する。認可エンドポイントURIは使用する認可サーバにより異なるため、認可サーバのアーキテクチャ仕様を確認されたい。
|
(9)
|
token-uri 属性には、認可サーバーのトークンエンドポイントURIを設定する。アクセストークンの取得やアクセストークンのリフレッシュを行う際に当トークンエンドポイントURIに対しリクエストを送信する。トークンエンドポイントURIは使用する認可サーバにより異なるため、認可サーバのアーキテクチャ仕様を確認されたい。
|
9.9.2.2.2.5. OAuth2AuthorizedClientManagerの実装¶
OAuth2AuthorizedClient
を管理するための、OAuth2AuthorizedClientManager
をBean定義する。OAuth2AuthorizedClientManager
が管理する内容及び処理についてはOAuth2AuthorizedClientManager/OAuth2AuthorizedClientProviderを参照されたい。以下にJavaConfigクラスを用いたBean定義の実装例を示す。
Note
本ガイドラインのBean定義は基本的にXML-based configurationを用いているが、OAuth2AuthorizedClientManager
のBean定義に関してはJava-based configurationを用いている。
OAuth2AuthorizedClientManager
に設定するOAuth2AuthorizedClientProvider
がBuilderパターンを使用していることに加え、Spring SecurityがOAuth2AuthorizedClientManager
に対するXML DLSを提供していないため、OAuth2AuthorizedClientManager/OAuth2AuthorizedClientProviderの実装例に従いJava-based configurationで実装している。Java-based configurationで定義するクラスは、コンポーネントスキャンが有効となるパッケージ配下に配置されたい。詳しくはJava-based configurationを参照されたい。
SecurityConfig.java
@Configuration
public class SecurityConfig {
// omitted
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
// (1)
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder
.builder()
.authorizationCode()
.refreshToken()
.build();
// (2)
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
// (3)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
項番 | 説明 |
---|---|
(1)
|
OAuth 2.0クライアントを認可(または再認可)するためのストラテジーを実装する。ここでは、認可コードグラントとリフレッシュトークンを設定している。
|
(2)
|
HttpServletRequest のコンテキスト内で操作するため、デフォルト実装であるDefaultOAuth2AuthorizedClientManager を実装する。
|
(3)
|
(2)で作成した
OAuth2AuthorizedClientManager に対しOAuth2AuthorizedClientProvider を設定する。これにより、OAuth2AuthorizedClientManager 経由でOAuth 2.0クライアントを認可することが可能となる。 |
9.9.2.2.2.6. アクセストークンの取得¶
認可サーバへのアクセスはOAuth2AuthorizedClientManager
及びSecurity Filterにより隠蔽されるため、認可サーバへのアクセスについて意識する必要はない。アクセストークンを取得するにはOAuth2AuthorizedClientManager
で管理しているOAuth2AuthorizedClient
を取得し、OAuth2AuthorizedClient
からアクセストークンを取得するだけでよい。
以下にServiceクラスの実装例を示す。
OAuth2TokenServiceImpl.java
@Service
public class OAuth2TokenServiceImpl implements OAuth2TokenService {
@Inject
private OAuth2AuthorizedClientManager authorizedClientManager;
// omitted
@Override
public OAuth2AccessToken getToken(String registrationId) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// (1)
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(registrationId)
.principal(authentication).build();
OAuth2AuthorizedClient authorizedClient = null;
try {
// (2)
authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
} catch (OAuth2AuthorizationException e) {
if ("invalid_grant".equals(e.getError().getErrorCode())) {
LOGGER.warn("refresh token expired. registrationId = {}", registrationId);
// (3)
throw new ClientAuthorizationRequiredException(registrationId);
}
throw e;
}
// (4)
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
return accessToken;
}
項番 | 説明 |
---|---|
(1)
|
registrationId によって識別されたOAuth2AuthorizedClient を認可するためのリクエストを作成する。 |
(2)
|
registrationId によって識別されたOAuth2AuthorizedClient の認可(または再認可)を行う。OAuth2AuthorizedClient が認可されていない場合は、AuthorizationCodeOAuth2AuthorizedClientProvider がClientAuthorizationRequiredException をスローし、Security Filterで捕捉することで認可処理を実行する。また、
OAuth2AuthorizedClient が認可済みの状態でアクセストークンの有効期限が切れている場合は、リフレッシュトークンによりアクセストークンがリフレッシュされる。
|
(3)
|
有効期限切れのリフレッシュトークンをリクエストした場合は、
OAuth2AuthorizationException がスローされ例外コードにinvalid_grant が設定される。ここでは、リフレッシュトークンの有効期限が切れていた場合に処理を継続させるため、(2)と同様に
ClientAuthorizationRequiredException をスローしOAuth2AuthorizedClient の認可処理を行っている。当処理は一例であるため、有効期限が切れた際にそのままエラー画面に遷移させたい場合など、要件に合わせ適切にエラーハンドリングを実施されたい。
|
(4)
|
認可済みの
OAuth2AuthorizedClient からアクセストークンを取得する。 |
9.9.2.2.2.7. リソースサーバへのアクセス¶
RestTemplateを用いてリソースサーバへアクセスする方法を説明する。
認可サーバへのアクセスはOAuth2AuthorizedClientManager
及びSecurity Filterにより隠蔽されるため、開発の際には認可サーバを意識する必要がなく、リソースサーバが提供するREST APIに対して行う処理を、通常のREST APIへのアクセスと同様に記述する。
以下にServiceクラスの実装例を示す。
TodoServiceImpl.java
@Service
public class TodoServiceImpl implements TodoService {
@Inject
private RestTemplate restTemplate; // (1)
@Inject
private OAuth2TokenService oAuth2TokenService;
@Value("${resource.serverUrl}/api/v1/todos")
String url;
@Override
public List<Todo[]> getTodoList(String registrationId) {
OAuth2AccessToken accessToken = this.oAuth2TokenService.getToken(registrationId); // (2)
RequestEntity<Void> requestEntity = RequestEntity
.get(this.url) // (3)
.headers(httpHeaders -> httpHeaders.setBearerAuth(accessToken.getTokenValue())) // (4)
.build();
ResponseEntity<Todo[]> responseEntity = this.restTemplate.exchange(requestEntity, Todo[].class); // (5)
return Arrays.asList(todoArray);
}
}
項番 | 説明 |
---|---|
(1)
|
RestTemplate をインジェクションする。 |
(2)
|
アクセストークンを取得する。アクセストークンの取得方法はアクセストークンの取得を参照されたい。
|
(3)
|
ここでは
GET メソッドを指定している。リソースサーバが提供するREST APIに合わせ適切に設定されたい。 |
(4)
|
アクセストークンでの認証を行うために、
Authorization: Bearer ヘッダに(2)で取得したアクセストークンを設定する。Authorization: Bearer ヘッダについてはRFC6750を参照されたい。 |
(5)
|
指定したURLとアクセストークンを使用しリソースサーバに対しRESTでアクセスし、結果をリストで受け取る。
|