はじめに
昨今のプログラミングブームにあやかり、SpringBootでWebアプリ作成をされている方の弟子になりたい非エンジニアのroughです。
仕事の日のお昼休憩は、花粉で目が開けられなくとも散歩にでる習性があります。
さて、今回は認証をカスタマイズしたプロジェクトにてmaximumSessionsが動作しなかった問題をテーマにします。
動作環境
Spring Boot 2.6.3
Java SE11
実装しようとしたこと
Spring Securityを使用して下記リファレンスを参考にしました。
同時セッション制御の項参照。下記コードを追加しました。
protected void configure(HttpSecurity http) throws Exception { http
.sessionManagement(session -> session .maximumSessions(1) .maxSessionsPreventsLogin(true) ); }
public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); }
問題点
これだけで動くなんて便利だなぁと思いながら動作確認も...動かず。
エラーなんてない...
カスタムしていないUsernameとPasswordのみの認証では動作する...
その日は衝撃で寝込みました。(初心者特有の打たれ弱さ)
解決までのプロセス
翌日、maximumSessions not workingで検索していると、UserDetailsのhashCodeやequalsをちゃんと書こうという旨の話を発見。
そういえば、「maximumSessionsってどう動いているの?」との疑問から旅が始まる。
初めに着目したのは先程掲載したリファレンスの同時セッション制御の項の少し下にある話だ。
SessionManagementFilter
は、SecurityContextRepository
のコンテンツをSecurityContextHolder
の現在のコンテンツと照合して、通常、事前認証や remember-me [ 1 ] などの非対話型認証メカニズムによって現在のリクエスト中にユーザーが認証されたかどうかを判断します。
...
次に、構成された
SessionAuthenticationStrategy
を呼び出します。
また、
実装では、
ConcurrentSessionControlAuthenticationStrategy
と呼ばれるSessionAuthenticationStrategy
の特殊バージョンを使用します。
この、SessionAuthenticationStrategyが呼び出される流れはアーキテクチャの項目にも記載がある。
フォームログインの項のUsernamePasswordAuthenticationFilterにて、
認証が成功した場合、成功。
SessionAuthenticationStrategy
に新しいログインが通知されます。
ここで分かったことは、maximumSessionで何かしらの処理をする前には、既にAuthenticationは作成されているという事実だ。
次に並行性制御で使用されるConcurrentSessionControlAuthenticationStrategy。
これを調べると、Authentication.getPrincipal()を使用してsessionの一覧を取得しているのがわかる。
次に認証後のAuthentication.getPrincipal()でカスタム認証有と無のプロジェクトで違いがあるか調べた。
すると、カスタム認証有のプロジェクトでは「【ユーザーのModel】@~」
一方、無のプロジェクトでは、「User[...」
前者の実装の原因を調べるとusernamePasswordAuthenticationTokenにて第一引数のPrincipalを設定しなくてはいけないところにユーザーのオブジェクトを直接保存していた。
これが原因で設定できなかったのかと設定しなおし、めでたく動作した。