質問: SpringSecurity初心者です。 Java 11 SpringBoot 2.3 でクライアントOauth2認証を実施しています。 トークンの管理は特にDBでしたりしていません。 Configクラスの設定で ``` @Autowired CustomOidcUserService serv; @Autowired CustomLogoutHandler logoutHandler; : protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and().oauth2Login() .userInfoEndpoint() .oidcUserService(serv).and().defaultSuccessUrl("/menu", true).failureUrl("/login?error"); http.logout().logoutSuccessUrl("/login").permitAll() .clearAuthentication(true).addLogoutHandler(logoutHandler); http.exceptionHandling().accessDeniedPage("/"); } ``` と設定して、ログアウト時にLogoutHandlerを利用して認証サーバー側のトークンを破棄することはできたのですが、セッションタイムアウト時に認証サーバー側のトークン破棄ができません。 セッションタイムアウトでセッションが破棄されているのは確認できたのですが、その後操作するとエラーとはならずセッションが生成され、新しいアクセストークンが作成されてしまいます。 詳しく説明を記述したいのですが、なにぶん初心者のため知識が少なく書き込める情報もわかっていません・・・。 ご迷惑承知ですが、ご教授のほうよろしくおねがいします。 ※補足※ を見ていますが、いまいち理解できないです。
回答: 自分も少し興味が有ったので考えてみました。 流れとしてはこういう感じになるかと思います。 1. Http Sessionの破棄時に何かをしたい、という場合には、`HttpSessionDestroyedEvent`のリスナを登録して、そこに処理を実装することになります。 こちらの具体的な方法については次のリンク先に説明があります。 * spring-securityのHttpSession生成・破棄イベントハンドラの作成 - Qiita ``` @Bean public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); } ``` また、この で`HttpSessionEventPublisher`をBean化して、リスナコンポーネントを作成する ``` @Component public class MySessionDestroyedEventListener implements ApplicationListener { @Override public void onApplicationEvent(final SessionDestroyedEvent event) { // 行いたい処理 } } ``` ということですね。 また、この`SessionDestroyedEvent`は(デフォルト設定のままであれば)http sessionのexpire時の他、logout時にも発生しますので、ログアウトのときは〜、session expireのときは〜、のように分けて考える必要はありません。 どちらの場合もここに実装すれば良いと思います(つまり、カスタムlogout handlerは必要ない)。 2. セッション破棄時に認可サーバへtoken revocation要求を行いたい、とのことですが、実際には、それ以外にもクライアント(ここではSpring Bootアプリ)が管理しているトークンも明示的に削除する必要がありました(さもなくば、そのトークンを使ってリソースサーバにアクセスできてしまう(例えばJWTを利用している場合))。 これは `OAuth2AuthorizedClientService#removeAuthorizedClient()` で実現できます。 まとめると、`SessionDestroyedEvent`リスナで * 認可サーバへのtoken revocation要求 * `OAuth2AuthorizedClientService`からのtoken削除 を行うことが、期待した機能を実現することになります。 GitHubを認可サーバ(&リソースサーバ)として今回の処理を行うサンプルを作ってみました(※実際にはもう少しちゃんと状態遷移について考える必要があるかもしれません): * `SessionDestroyedEvent`リスナとtokenの削除処理2種が上記で記載したポイントになります。 追記: コメントでも触れたのですが、Spring Security系列のOAuth2.0関連実装は複数あります(ありました)。 補足として示されているTERASOLUNAのリファレンス(最新版はこちら)で説明されているのはSpring Security OAuthというプロダクト/プロジェクトですが、これはもはやdeprecatedです。 現在、OAuth2.0に関する機能はSpring Security本体のプロジェクトに取り込まれています。 これら2つは別物ですので、ドキュメントは参考になりません。 (ちょっと古いですがここに状況を簡単にまとめています。) TERASOLUNAのリファレンスには > > Spring Security OAuthのデフォルト実装ではセッションスコープでアクセストークンを保持する > > > とありますが、Spring SecurityのOAuth 2.0 Client (紛らわしいですが前述の通りSpring Security OAuthとは別物です)ではセッションとは独立して管理している(本文に記載している通り`OAuth2AuthorizedClientService`が管理)ので、ここが混乱されている一因なのかな、と思いました。 追記2: こちらのコメントで触れたkazuki43zooさんの記事 * 第1回:Spring Security 5でサポートされるOAuth 2.0 LoginをSpring Bootで使ってみる > ログイン中にアクセストークンを破棄してみる - Qiita の、 > > Spring Securityのデフォルト動作では、認証が成功すると認証情報がHTTPセッションで管理される仕組みになっているため、ログアウトまたはセッションタイムアウトするまでログイン状態は維持されます。 > > > という説明が気になった(私の回答と矛盾しているように見える、つまり本回答が間違っているかもしれない)ので調べてみました。 (ちなみにここで「アクセストークンを破棄してみる」と言っているのは、 でrevokeすることを指しているはずです。) この引用文中で「認証情報」と呼んでいるのは、(デフォルトでは)`OAuth2AuthenticationToken`及びこれが`principal`フィールドに持つ`OAuth2User`のようです。 この情報によってセッションを利用しているSpringBootログインユーザと`OAuth2AuthorizedClient`が紐付けられます。 トークンを管理しているのは`OAuth2AuthorizedClient`なので、セッションの破棄が即ちトークンの破棄というわけではなく(SpringBootログインユーザとGitHubユーザの紐付けが無くなるだけ)、やはり今回の場合明示的に`OAuth2AuthorizedClientService#removeAuthorizedClient()`を呼ぶべきかなと考えます。 というわけで、本文の対応で問題無さそうでした。 追記3: コメントの疑問について。 > > 今回のようにセッションが破棄されたタイミングで認証サーバへトークンの破棄というのは通常しない行為なのでしょうか...? > > > RFC6749訳の登場人物のところに説明がありますが、 リソースオーナーがクライアントに認可を与えた、ということは、**クライアントが「リソースオーナーの代理として保護されたリソースに対するリクエストを行」なえるようになった**、ということです。 今回、 * リソースオーナー = SpringBootユーザ * クライアント = SpringBootアプリケーション となるわけですが、SpringBootアプリケーションがリソースサーバへアクセスするのにSpringBootユーザがログインしている必要がある、というような制約はOAuth2.0的にはありません(のでSpring Security OAuth2.0 Clientの実装もそうなっている)。クライアントはもはや認可を得ており、その権限内でリソースオーナーの指示無く自発的に保護されたリソースへアクセスすることができます。 また、世にあるアプリケーションがどうしているかというと、やはりログアウト時に認可をrevokeするようなものは少ないのではないかと思います。 例えば、いわゆるソーシャルログイン機能として認可を与えているサービス一覧が次のページで見られますが、ログアウトしたからと言ってこの一覧から消えるようなサービスはあまり無いのではないでしょうか。 * Google: * Twitter: * GitHub: * Facebook: (ただ、ログインと同時に認可をrevokeするようなアプリケーションは存在し得ないか、というとそういうわけでもなく、要件次第、ということになるでしょうか...) > > 破棄しないとトークンが破棄されず溜まる一方なのかと思っているのですが... > > > これはその通りです。ただ、1ユーザ当たり1認可サーバに対して必要なトークンは(通常)最大でも1つなので、際限なく増加するわけではなく上限があます。 また、`OAuth2AuthorizedClientService`の実装はデフォルトではインメモリ管理ですが、プロダクションに適用するのであれば、このままだと揮発してしまうので`JdbcOAuth2AuthorizedClientService`を使ってDBに永続化したりすることになると思います。ですので、あまり気にする部分でもないのかなと考えます。 (これも要件次第ではありますが...) 追記4: 本件と少し関係のある話題が次のチケットで議論されていました: * Use HttpSessionOAuth2AuthorizedClientRepository instead of AuthenticatedPrincipalOAuth2AuthorizedClientRepository as bean. #24237 Spring Boot の auto-configuration で設定される`InMemoryOAuth2AuthorizedClientService`(今回これを使う想定だと思います) は development / testing 向けで、productionでの利用は推奨していない、などが説明されています。