67763


SpringSecurity初心者です。

Java 11  
SpringBoot 2.3  
でクライアントOauth2認証を実施しています。  
トークンの管理は特にDBでしたりしていません。

Configクラスの設定で

[code]

        @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("/");
    }
    
[/code]

と設定して、ログアウト時にLogoutHandlerを利用して認証サーバー側のトークンを破棄することはできたのですが、セッションタイムアウト時に認証サーバー側のトークン破棄ができません。

セッションタイムアウトでセッションが破棄されているのは確認できたのですが、その後操作するとエラーとはならずセッションが生成され、新しいアクセストークンが作成されてしまいます。

詳しく説明を記述したいのですが、なにぶん初心者のため知識が少なく書き込める情報もわかっていません・・・。  
ご迷惑承知ですが、ご教授のほうよろしくおねがいします。

※補足※  
<https://terasolunaorg.github.io/guideline/5.3.0.RELEASE/ja/Security/OAuth.html#oauthclientserverhowtocanceltoken>  
を見ていますが、いまいち理解できないです。


CC BY-SA 4.0
2020-06-16T08:07:20.387
67716
2021-03-25T05:47:19.197
2020-06-17T04:02:02.357
27196
27196
question
spring-boot oauth spring-security
SpringBoot Oauth2でクライアント認証をする際、sessionが破棄と同時に認証サーバー側のトークンをrevokeする方法について


自分も少し興味が有ったので考えてみました。  
流れとしてはこういう感じになるかと思います。

* * *

  1. 

Http
Sessionの破棄時に何かをしたい、という場合には、`HttpSessionDestroyedEvent`のリスナを登録して、そこに処理を実装することになります。  
こちらの具体的な方法については次のリンク先に説明があります。

  * [spring-securityのHttpSession生成・破棄イベントハンドラの作成](https://qiita.com/kagamihoge/items/f27590884870a41dff4e) \- Qiita

[code]

        @Bean
        public HttpSessionEventPublisher httpSessionEventPublisher() {
            return new HttpSessionEventPublisher();
        }
    
[/code]

また、この  
で`HttpSessionEventPublisher`をBean化して、リスナコンポーネントを作成する

[code]

    @Component
    public class MySessionDestroyedEventListener implements ApplicationListener<SessionDestroyedEvent> {
        @Override
        public void onApplicationEvent(final SessionDestroyedEvent event) {
            // 行いたい処理
        }
    }
    
[/code]

ということですね。

また、この`SessionDestroyedEvent`は([デフォルト設定](https://github.com/spring-
projects/spring-
security/blob/5.3.3.RELEASE/web/src/main/java/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.java#L46)のままであれば)http
sessionのexpire時の他、logout時にも発生しますので、ログアウトのときは〜、session
expireのときは〜、のように分けて考える必要はありません。  
どちらの場合もここに実装すれば良いと思います(つまり、カスタムlogout handlerは必要ない)。

2.

セッション破棄時に認可サーバへtoken revocation要求を行いたい、とのことですが、実際には、それ以外にもクライアント(ここではSpring
Bootアプリ)が管理しているトークンも明示的に削除する必要がありました(さもなくば、そのトークンを使ってリソースサーバにアクセスできてしまう(例えば[JWT](https://stackoverflow.com/a/46215392/4506703)を利用している場合))。  
これは `OAuth2AuthorizedClientService#removeAuthorizedClient()` で実現できます。

まとめると、`SessionDestroyedEvent`リスナで

  * 認可サーバへのtoken revocation要求
  * `OAuth2AuthorizedClientService`からのtoken削除

を行うことが、期待した機能を実現することになります。

* * *

GitHubを認可サーバ(&リソースサーバ)として今回の処理を行うサンプルを作ってみました(※実際にはもう少しちゃんと状態遷移について考える必要があるかもしれません):

  * <https://github.com/yukihane/stackoverflow-qa/tree/master/so67716>

[`SessionDestroyedEvent`リスナ](https://github.com/yukihane/stackoverflow-
qa/blob/master/so67716/src/main/java/com/example/so67716/MySessionDestroyedEventListener.java)と[tokenの削除処理2種](https://github.com/yukihane/stackoverflow-
qa/blob/f0be790cb707379395ed9d7923ce1bde1c6a8138/so67716/src/main/java/com/example/so67716/GitHubService.java#L25-L29)が上記で記載したポイントになります。

* * *

追記:  
[コメント](https://ja.stackoverflow.com/questions/67716/springboot-
oauth2%e3%81%a7%e3%82%af%e3%83%a9%e3%82%a4%e3%82%a2%e3%83%b3%e3%83%88%e8%aa%8d%e8%a8%bc%e3%82%92%e3%81%99%e3%82%8b%e9%9a%9b-session%e3%81%8c%e7%a0%b4%e6%a3%84%e3%81%a8%e5%90%8c%e6%99%82%e3%81%ab%e8%aa%8d%e8%a8%bc%e3%82%b5%e3%83%bc%e3%83%90%e3%83%bc%e5%81%b4%e3%81%ae%e3%83%88%e3%83%bc%e3%82%af%e3%83%b3%e3%82%92revoke%e3%81%99%e3%82%8b%e6%96%b9%e6%b3%95%e3%81%ab%e3%81%a4%e3%81%84%e3%81%a6#comment74318_67716)でも触れたのですが、Spring
Security系列のOAuth2.0関連実装は複数あります(ありました)。  
補足として示されているTERASOLUNAのリファレンス(最新版は[こちら](http://terasolunaorg.github.io/guideline/5.5.1.RELEASE/ja/Security/OAuth.html#overview))で説明されているのは[Spring
Security OAuth](https://spring.io/projects/spring-security-
oauth)というプロダクト/プロジェクトですが、これはもはやdeprecatedです。  
現在、OAuth2.0に関する機能は[Spring Security本体](https://docs.spring.io/spring-
security/site/docs/current/reference/html5/#spring-security-
oauth2-core)のプロジェクトに取り込まれています。  
これら2つは別物ですので、ドキュメントは参考になりません。  
(ちょっと古いですが[ここ](https://qiita.com/yukihane/items/fc97f888ecb6a6850ea7#spring-
boot216%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B-oauth-20-%E5%AF%BE%E5%BF%9C)に状況を簡単にまとめています。)

TERASOLUNAのリファレンスには

> Spring Security OAuthのデフォルト実装ではセッションスコープでアクセストークンを保持する

とありますが、Spring SecurityのOAuth 2.0 Client (紛らわしいですが前述の通りSpring Security
OAuthとは別物です)ではセッションとは独立して管理している(本文に記載している通り`OAuth2AuthorizedClientService`が管理)ので、ここが混乱されている一因なのかな、と思いました。

* * *

追記2:

[こちらのコメント](https://ja.stackoverflow.com/questions/67716/springboot-
oauth2%e3%81%a7%e3%82%af%e3%83%a9%e3%82%a4%e3%82%a2%e3%83%b3%e3%83%88%e8%aa%8d%e8%a8%bc%e3%82%92%e3%81%99%e3%82%8b%e9%9a%9b-session%e3%81%8c%e7%a0%b4%e6%a3%84%e3%81%a8%e5%90%8c%e6%99%82%e3%81%ab%e8%aa%8d%e8%a8%bc%e3%82%b5%e3%83%bc%e3%83%90%e3%83%bc%e5%81%b4%e3%81%ae%e3%83%88%e3%83%bc%e3%82%af%e3%83%b3%e3%82%92revoke%e3%81%99%e3%82%8b%e6%96%b9%e6%b3%95%e3%81%ab%e3%81%a4%e3%81%84%e3%81%a6#comment74377_67763)で触れたkazuki43zooさんの記事

  * [第1回：Spring Security 5でサポートされるOAuth 2.0 LoginをSpring Bootで使ってみる > ログイン中にアクセストークンを破棄してみる](https://qiita.com/kazuki43zoo/items/658eabbf0d6a60d3ed89#%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E4%B8%AD%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E7%A0%B4%E6%A3%84%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B) \- Qiita

の、

> Spring
> Securityのデフォルト動作では、認証が成功すると認証情報がHTTPセッションで管理される仕組みになっているため、ログアウトまたはセッションタイムアウトするまでログイン状態は維持されます。

という説明が気になった(私の回答と矛盾しているように見える、つまり本回答が間違っているかもしれない)ので調べてみました。  
(ちなみにここで「アクセストークンを破棄してみる」と言っているのは、 <https://github.com/settings/applications>
でrevokeすることを指しているはずです。)

この引用文中で「認証情報」と呼んでいるのは、(デフォルトでは)[`OAuth2AuthenticationToken`](https://github.com/spring-
projects/spring-
security/blob/5.3.3.RELEASE/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthenticationToken.java)及びこれが`principal`フィールドに持つ`OAuth2User`のようです。

この情報によってセッションを利用しているSpringBootログインユーザと`OAuth2AuthorizedClient`が紐付けられます。  
トークンを管理しているのは[`OAuth2AuthorizedClient`](https://github.com/spring-
projects/spring-
security/blob/5.3.3.RELEASE/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java#L47-L48)なので、セッションの破棄が即ちトークンの破棄というわけではなく(SpringBootログインユーザとGitHubユーザの紐付けが無くなるだけ)、やはり今回の場合明示的に`OAuth2AuthorizedClientService#removeAuthorizedClient()`を呼ぶべきかなと考えます。

というわけで、本文の対応で問題無さそうでした。

* * *

追記3:  
[コメント](https://ja.stackoverflow.com/questions/67716/springboot-
oauth2%e3%81%a7%e3%82%af%e3%83%a9%e3%82%a4%e3%82%a2%e3%83%b3%e3%83%88%e8%aa%8d%e8%a8%bc%e3%82%92%e3%81%99%e3%82%8b%e9%9a%9b-session%e3%81%8c%e7%a0%b4%e6%a3%84%e3%81%a8%e5%90%8c%e6%99%82%e3%81%ab%e8%aa%8d%e8%a8%bc%e3%82%b5%e3%83%bc%e3%83%90%e3%83%bc%e5%81%b4%e3%81%ae%e3%83%88%e3%83%bc%e3%82%af%e3%83%b3%e3%82%92revoke%e3%81%99%e3%82%8b%e6%96%b9%e6%b3%95%e3%81%ab%e3%81%a4%e3%81%84%e3%81%a6#comment74393_67763)の疑問について。

> 今回のようにセッションが破棄されたタイミングで認証サーバへトークンの破棄というのは通常しない行為なのでしょうか…？

[RFC6749訳の登場人物](https://openid-foundation-
japan.github.io/rfc6749.ja.html#anchor2)のところに説明がありますが、  
リソースオーナーがクライアントに認可を与えた、ということは、
**クライアントが「リソースオーナーの代理として保護されたリソースに対するリクエストを行」なえるようになった** 、ということです。  
今回、

  * リソースオーナー = SpringBootユーザ
  * クライアント = SpringBootアプリケーション

となるわけですが、SpringBootアプリケーションがリソースサーバへアクセスするのにSpringBootユーザがログインしている必要がある、というような制約はOAuth2.0的にはありません(のでSpring
Security OAuth2.0
Clientの実装もそうなっている)。クライアントはもはや認可を得ており、その権限内でリソースオーナーの指示無く自発的に保護されたリソースへアクセスすることができます。

また、世にあるアプリケーションがどうしているかというと、やはりログアウト時に認可をrevokeするようなものは少ないのではないかと思います。  
例えば、いわゆるソーシャルログイン機能として認可を与えているサービス一覧が次のページで見られますが、ログアウトしたからと言ってこの一覧から消えるようなサービスはあまり無いのではないでしょうか。

  * Google: <https://myaccount.google.com/permissions>
  * Twitter: <https://twitter.com/settings/applications>
  * GitHub: <https://github.com/settings/applications>
  * Facebook: <https://www.facebook.com/settings?tab=applications>

(ただ、ログインと同時に認可をrevokeするようなアプリケーションは存在し得ないか、というとそういうわけでもなく、要件次第、ということになるでしょうか…)

> 破棄しないとトークンが破棄されず溜まる一方なのかと思っているのですが…

これはその通りです。ただ、1ユーザ当たり1認可サーバに対して必要なトークンは(通常)最大でも1つなので、際限なく増加するわけではなく上限があます。  
また、`OAuth2AuthorizedClientService`の実装はデフォルトでは[インメモリ管理](https://github.com/spring-
projects/spring-
security/blob/5.3.3.RELEASE/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryOAuth2AuthorizedClientService.java)ですが、プロダクションに適用するのであれば、このままだと揮発してしまうので[`JdbcOAuth2AuthorizedClientService`](https://github.com/spring-
projects/spring-
security/blob/5.3.3.RELEASE/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JdbcOAuth2AuthorizedClientService.java)を使ってDBに永続化したりすることになると思います。ですので、あまり気にする部分でもないのかなと考えます。  
(これも要件次第ではありますが…)

* * *

追記4:

本件と少し関係のある話題が次のチケットで議論されていました:

  * [Use HttpSessionOAuth2AuthorizedClientRepository instead of AuthenticatedPrincipalOAuth2AuthorizedClientRepository as bean. #24237](https://github.com/spring-projects/spring-boot/issues/24237)

Spring Boot の auto-configuration
で設定される`InMemoryOAuth2AuthorizedClientService`(今回これを使う想定だと思います) は development /
testing 向けで、productionでの利用は推奨していない、などが説明されています。


CC BY-SA 4.0
2020-06-17T16:17:34.650
67763
2021-03-25T05:47:19.197
2021-03-25T05:47:19.197
2808
2808
67716
answer