Sprint Security で login URL と login 画面の両方を変更する

## 概要

Spring Boot + Spring Security を用いたユーザーアカウント管理システムにおいて，独自の login 画面を作成し，login ページの URL を `/login` ではなく `/signin` とするのに少しだけ手間取った．

最初に結論を示し，続けて参考のために過程を伴う詳細を記す．

## 前提

* Spring Security の機能を利用して，ユーザーアカウントの作成，ログイン，ログアウトの最低限の処理を実装したことがある．

* 独自の login 画面を用いたログイン処理を実装したことがない方は，先に[詳細](https://qiita.com/drafts#%E8%A9%B3%E7%B4%B0)を参照されたい．

## 環境

* Spring Boot 2.4.4
* Spring Security
* Java 11
* Thymeleaf

## 結論

Config，Controller，テンプレートファイルの login 画面表示 URL および login 処理実行 URL に，利用したい値 (ここでは `/signin`) を指定することで解決する．

```WebSecurityConfiguration.java
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
  /* 中略 */
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http./* 中略 */
        .formLogin()
        .loginPage("/signin") // 追記または変更; デフォルト login 画面表示 URL
  }
}
```

```AccountController.java
@GetMapping("/signin") // 変更
public ModelAndView login(ModelAndView modelAndView) {
  modelAndView.setViewName("login");
  return modelAndView;
}
```

```login.html
<form th:method="post" th:action="@{/signin}"> <!-- 変更 -->
  <input type="text" name="username"></td>
  <input type="password" name="password"></td>
  <input type="submit" value="Sign in"/></td>
</form>
```

## 詳細

デフォルトの login ページ URL は `/login` なので，login 画面用のテンプレートファイル `login.html` (ファイル名はこれに限らない) を作成し，login 用の form に `th:action="@{/login}"` を追加すれば， login 画面を独自のテンプレートファイルに差し替えることができる．

```login.html
<form th:method="post" th:action="@{/login}">
  <input type="text" name="username"></td>
  <input type="password" name="password"></td>
  <input type="submit" value="Sign in"/></td>
</form>
```

ここで，login 画面を表示する URL を `/signin` に変更したいとする．

Controller のマッピング URL を変更し，それ以外はデフォルト (`/login`) とした場合，`/signin` にアクセスをすれば login 処理は実行できる．

```AccountController.java
@GetMapping("/signin")
public ModelAndView login(ModelAndView modelAndView) {
}
```

ただし，Spring Security で「認証済みでない場合に，許可されていないページにアクセスをするとログインページにリダイレクトされる」機能を利用していると，デフォルトのリダイレクト先が `/login` であるため独自の login ページが表示されない．

次に，デフォルトの login 画面表示 URL を `/signin` に変更するために Config に次の記述を追加する．これに伴い，認証なしで `/signin` にアクセスできるよう設定する．

```WebSecurityConfiguration.java
    http.authorizeRequests()
          .mvcMatchers("/", "/signin").permitAll() // 認証なしで /signin にアクセス可能
        .and
        .formLogin()
          .loginPage("/signin") // デフォルト login 画面表示 URL の変更
```

このとき，login 処理をするための POST リクエストの URL まで変更される模様．リダイレクトされても独自の login ページが表示されるが，今度は submit をしても login 処理ができなくなる．

テンプレートファイルの form の遷移先も同様に変更することでこの問題は解決する．

```login.html
<form th:method="post" th:action="@{/signin}">
```

## login 画面を表示する GET リクエスト URL と login 処理を実行する POST リクエスト URL に別の値を指定する

次のように URL を指定する場合の記述を示す．

| 機能 | URL | HTTP メソッド |
|:---|:---|:---|
| login 画面表示 | /signin/form | GET |
| login 処理実行 | /signin/execute | POST |

```WebSecurityConfiguration.java
    http.authorizeRequests()
          .mvcMatchers("/", "/signin/**").permitAll() // 変更: /signin -> /signin/**
        .and
        .formLogin()
          .loginPage("/signin/form") // 変更: /signin -> /signin/form
          .loginProcessingUrl("/signin/execute") // 追加
```

```AccountController.java
@GetMapping("/signin/form") // 変更: /signin -> /signin/form
public ModelAndView login(ModelAndView modelAndView) {
  modelAndView.setViewName("login");
  return modelAndView;
}
```

```login.html
<form th:method="post" th:action="@{/signin/execute}"> <!-- 変更 -->
  <input type="text" name="username"></td>
  <input type="password" name="password"></td>
  <input type="submit" value="Sign in"/></td>
</form>
```