Spring Securityをカスタマイズしたソーシャルログイン
アソビュー Advent Calendar 2021の9日目です。
こんにちは。アソビュー!バックエンドエンジニアの山野です。
昨年のAdvent Calendar でSpring SecurityのOAuth Clientを使った
簡単にソーシャルログイン機能を実装する方法ついて書きました。
今回はこの機能をカスタマイズしリクエストによって1つのアプリケーションで認証処理を切り替える方法にについて書きたいと思います。
単一のアプリケーションでのみ利用する認証機能など、
シンプルなアプリケーション構成であれば、Spring Securityのデフォルトの機能を利用する形で問題ないと思いますが、
例えば複数サービスから呼ばれる共通の認証用アプリケーションで、
サービスに応じて異なる認証を行いたい、或いは、異なる認証情報を取得したいなどより複雑な認証を行うニーズもあると思います。
そのような場合に今回の内容が参考になればと思います。
カスタマイズの説明の前に
カスタマイズの説明に入るする前に、
少しSpring Security OAuth Clientの機能のおさらいとして、デフォルトの設定での認証機能の実装方法を説明します。
ログイン画面等の表示処理部分は割愛しますが認証を行うには以下の実装だけで実現できます。
Spring Securityは設定ファイル(application.properties/yaml)に指定のフォーマットでログイン認証に必要な情報(認証プロバイダ(ソーシャルログインサービス)のClient ID・認可エンドポイントなど)を記載するば、少しのコード実装でログイン機能が作れます。
またGoogleなどの特定の認証プロバイダ(ソーシャルログインサービス)については、認可エンドポイントなどの情報を実装者が明示的に書かなくても、Spring Securityの方でそれらの情報を補完してくれる仕組みを持っています。
設定ファイル例
認証設定クラス
認証機能のカスタマイズ
では本題の認証機能のカスタマイズする実装について説明したいと思います。
今回は以下のような簡易アプリを作りたいと思います。
- App1・App2という異なる2つのページがあるとする。- [App1ページからログインした場合]
->ユーザー情報としてメールアドレスのみ取得する。- [App2ページからログインした場合]
->ユーザー情報としてメールアドレス + プロフィールの情報を取得する。- ログイン後、取得した情報を表示する。
完成形のイメージ
今回リクエストによって異なる認証を行う方法として、
リクエストURLのルートパスを基準とし、認証処理を切り替える方法を取ります。
これを実現した実装が以下となります。
実装にあたっては4点ポイントがあります。
実装例のコード上にも番号付きでコメントを書いています。
1. 特定のルートパスを認証処理対象に設定する。
2. 明示的にAuthorization Endpointの指定する。
3. 明示的にRedirect Endpointの指定する。
4. Javaでソーシャルログイン認証設定の実装。
実装ポイント詳細
それぞれポイントについて説明します。
1. 特定のルートパスを認証処理対象に設定する。
こちらについてはタイトルの通りでリクエストが指定したルートパスに合致した場合に認証処理を行う設定を入れています。
2. 明示的なAuthorization Endpointの指定する。
アプリケーションからログインを実行→ソーシャルログイン側にリダイレクトするまでの仕組みは以下のようになっています。
ログイン実行
↓
ログイン実行をハンドリング
↓
リクエストの内容と設定ファイル(application.properties)の情報を元に、
認証プロバイダへリダイレクトするためにURLの生成。
↓
認証プロバイダへリダイレクト
このログイン実行後以降の処理はSpring SecurityのOAuth2AuthorizationRequestRedirectFilterクラスで行われており、
Spring Securityのデフォルトの設定では以下のURLパスにアクセスがされるとこのクラスが動作する仕組みとなっています。
/oauth2/authorize/{registrationID}
しかし今回のように特定のルートパスを認証対象と設定をした場合、「/oauth2/authorize/{registrationID}」のURLは無効なエンドポイントとなります。
よって明示的にログイン実行を表すパスを設定し、上記のクラスにアクセスをハンドリングさせるようにする必要があります。
またパスは下記の形式である必要があります。
/{認証対象のルートパス}/{任意のパス}/{registrationID}
今回の実装例では「/app1/authorize/・/app2/authorize/」というパスを設定しました。
またこれに付随してログイン画面のログインボタン押下後の遷移先は、
「/app1/authorize/{registrationID}」・「/app2/authorize/{registrationID}」とする必要があります。
3. 明示的なRedirect Endpointの指定
ここでいうRedirectは今回の例でいうApp1/App2ページ → 認証プロバイダ(ソーシャルログインサービス)へのリダイレクトではなく、
ログイン認証後に 認証プロバイダ → App1/App2ページへの戻り先のことを指します。
Spring Securityには認証プロバイダからコールバックからされると、そのリクエストをハンドリングし、プロバイダからのレスポンス情報をもとにアクセストークンの発行やリソース情報の取得を行うクラス(OAuth2LoginAuthenticationFilter)が存在します。
このクラスのデフォルトの設定では以下のパスにリクエストされると上記の処理が実行される仕組みとなっています。
/login/oauth2/code/{registrationID}
しかし、Authorization Endpointの場合と同じく特定のパスを認証対象とした場合は下記のURL形式で明示的に指定する必要があります。
/{認証対象のルートパス}/{任意のパス}/{registrationID}
実装例では「/app1/login/code/」・「/app2/login/code/」というパスを設定しました。
4. Javaでソーシャルログイン認証設定の実装
「カスタマイズの説明の前に」のセクションでも書いた通り、
Spring Securityは設定ファイル(application.properties/yaml)に指定のフォーマットで記載することでログイン機能が作れるできる仕組みとなっています。
ただ今回のようにリクエストURLのパスによって認証の処理を分ける場合、「3. 明示的なRedirect Endpointの指定」のセクションの内容と関係し以下の課題があります。
- redirect-url(コールバックURL)が認証対象のパスによって異なる。
- Spring Securityのフォーマットに合わせて書くとなると、
認証プロバイダ × 認証対象のパスの数だけ設定情報を記述する必要がある。
- redirect-url(コールバックURL)以外はほとんど同じ内容となるので
冗長な記載になってしまう。
設定例 (Google認証の設定をApp1とApp2それぞれ書くこととなる。)
これでも構わないというのであれば以降の内容はスキップで構いませんが、Spring Securityで用意されているクラスで、設定ファイル(application.properties/yaml)に頼らず、Javaでこのソーシャルログイン設定の処理を実装することができます。
またJavaの利点を生かし、部分的な処理の共通化ができ冗長な表現が解消できます。
今回の実装例では以下の形にしています。
- ソーシャルログイン設定の共通メソッドを用意。- App1とApp2で設定が異なる項目※をメソッドの引数として受け取るようにする。
(※認証プロバイダ→アプリケーションへのコールバックURL・取得するユーザー情報)- App1/App2の認証設定部分で上記の共通メソッドを呼び出す。
この時コールバックURL・取得するユーザー情報(scope)を引数として渡す。
ソーシャルログイン設定メソッド部分
またGoogle認証の場合はSpring Securityのクラス(CommonOAuthProvider)によって、認可エンドポイント等の設定の実装を書かなくても良くなります。
App1の認証設定
以下を引数として渡す。
redirect URL : http://localhost:8080/app1/login/code/google
scope : openid・email
App2の認証設定
以下を引数として渡す。
redirect URL : http://localhost:8080/app2/login/code/google
scope : openid・email・profile
このようにSpring Secuity の仕組みを理解するとより柔軟な認証が実現できます。
今回は以上です!
さいごに
「生きるに、遊びを。」をミッションに掲げる我らがアソビューではアソビューではエンジニアを募集しています。
ご興味ある方はぜひ応募ください!