UsernamePasswordAuthenticationFilter を継承したクラスを使ったらセッションIDが変わらなかった時のメモ

`Spring Security` の `UsernamePasswordAuthenticationFilter` はユーザー名/パスワードを使って認証を行うフィルタです。

リクエストの本文がJSONの時はこのままではうまくいきません。(`HttpServletRequest#getParameter` を使っているため)
なので、このクラスを継承した自前のクラスを作成しました。(このフィルタクラス自体の内容は省略)

この自前のクラスを登録するときに

``` java

	@Override
	protected void configure(HttpSecurity http) throws Exception {
    //省略

		JsonBodyUsernamePasswordAuthenticationFilter f = new JsonBodyUsernamePasswordAuthenticationFilter();
    //いくつか省略
		http.addFilterAt(f, UsernamePasswordAuthenticationFilter.class);
	}
```

としていたのですが、これだと一部の機能が `UsernamePasswordAuthenticationFilter` と同じようには動きません。
(例 ログイン成功時にセッションIDが変更されないなど)

この問題の対処のメモを書きます。

- `Spring Boot` のバージョンは2.5.5
- `Spring Security` のバージョンは5.5.2

です。

## 先に結論
フィルターの登録には 上記の方法は使わず [FormLoginConfigurer](https://github.com/spring-projects/spring-security/blob/5.5.2/config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java) を参考に `AbstractAuthenticationFilterConfigurer` を継承したクラスを作成し、このクラス経由でフィルターを登録するようにしました。

コード例

``` java
public class JsonBodyLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
		AbstractAuthenticationFilterConfigurer<H, JsonBodyLoginConfigurer<H>, JsonBodyUsernamePasswordAuthenticationFilter> {

	public JsonBodyLoginConfigurer() {
		super(new JsonBodyUsernamePasswordAuthenticationFilter(), null);
	}

	@Override
	public JsonBodyLoginConfigurer<H> loginPage(String loginPage) {
		return super.loginPage(loginPage);
	}

	@Override
	protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
		return new AntPathRequestMatcher(loginProcessingUrl, "POST");
	}

}
```

``` java

	@Override
	protected void configure(HttpSecurity http) throws Exception {
    //省略
		http.apply(new JsonBodyLoginConfigurer<>())
				.loginPage("/login")
				.loginProcessingUrl("/login")
				.permitAll()
        //省略
	}
```

これで ログイン成功時にセッションIDが変更されるようになりました。


## セッションIDの変更はどこでされているのか？

[ChangeSessionIdAuthenticationStrategy](https://github.com/spring-projects/spring-security/blob/5.5.2/web/src/main/java/org/springframework/security/web/authentication/session/ChangeSessionIdAuthenticationStrategy.java) で行われています。

このメソッドは [AbstractAuthenticationProcessingFilter](https://github.com/spring-projects/spring-security/blob/5.5.2/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java#L227) (`UsernamePasswordAuthenticationFilter` の親クラス)で認証成功時に呼ばれるようになっています。

`UsernamePasswordAuthenticationFilter` で検証したときは [CompositeSessionAuthenticationStrategy](https://github.com/spring-projects/spring-security/blob/5.5.2/web/src/main/java/org/springframework/security/web/authentication/session/CompositeSessionAuthenticationStrategy.java) で集約されており、他には [CsrfAuthenticationStrategy](https://github.com/spring-projects/spring-security/blob/5.5.2/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java) が含まれていました。

(この `CompositeSessionAuthenticationStrategy` はどこで作られているかは追ってないです)

## なぜ セッションIDが変更されなかったのか?

`AbstractAuthenticationProcessingFilter` で宣言されている `sessionStrategy` フィールド

``` java
private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
```

が `NullAuthenticatedSessionStrategy` のままになっていました。

このため、認証に成功した場合もセッションIDの変更が行われないままになっていました。


## `sessionStrategy` はどこでセットされるのか
コードを追いかけたところ [AbstractAuthenticationFilterConfigurer#configure](https://github.com/spring-projects/spring-security/blob/5.5.2/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java#L284) メソッドで セットされていました。

このメソッドでは `SessionAuthenticationStrategy` 以外の クラスもセットしているようです。

`addFilterAt` で登録したときは、これらのクラスのセットが行われていなかったので、セッションIDの変更が行われなかったようです。

## じゃあどうしようか？🤔

ここで設定しているように `http.getSharedObject(SessionAuthenticationStrategy.class)` を `WebSecurityConfigurerAdapter` の継承クラスの`configure` メソッドで使おうとしましたが、このタイミングでは `get` してもまだ設定されていないので `null` が返却されます。

悩んだ結果 `AbstractAuthenticationFilterConfigurer` の継承クラスを使うのが良いと思い、最初に記載した方法になりました。


## 終わり
この方法が正しいかどうかもわかりませんが、とりあえずメモとして残しておきます。