Spring Boot + Spring SecurityでURIに認証情報(ユーザ名)を使用したかった話

# 概要
タイトル通りです。
QiitaっぽいURIにしたかったのでやってみました。
ユーザ名を`hoge`とすると
**修正前**
`http://localhost:8080/menu`
**修正後**
`http://localhost:8080/hoge/menu`
のようにしたいです。

# 環境
`spring-boot-starter-security: 2.0.2.RELEASE`
`spring-boot-starter-thymeleaf: 2.0.2.RELEASE`
`spring-boot-starter-web: 2.0.2.RELEASE`
`thymeleaf-extras-springsecurity4: 3.0.2.RELEASE`
`thymeleaf-layout-dialect: 2.3.0`

# 実装
認証処理については偉大な先人の方達の記事を見て実装済みとします。
では、Entityから。

### ユーザエンティティ

```java:User.java
@Data
@Entity
public class User implements UserDetails {
    /** ユーザID */
    private String userId;

    /** パスワード */
    private String password;

    /** ユーザ名 */
    private String userName;

// ~省略~

}
```

### ログイン時に遷移するmenu画面用controller

```java:MenuController.java
@Controller
public class MenuController {
    @RequestMapping(value = "{userName}/menu")
    public String menu(@PathVariable("userName") String userName,
            @AuthenticationPrincipal User userDetail) {
        // 認証情報が存在しない場合、ログイン画面にリダイレクト
        if (userDetail == null) {
            return "redirect:login/login";
        }

        // @PathVariableに指定したuserNameを色々使用することも可能
        // System.out.println(userName);

        return "menu";
    }
}

```
  
### menu画面遷移に使用するheaderテンプレート

```html:header.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
  <body>
    <header class="navbar" layout:fragment="header">
      <div class="container">
        <a href="/">
          Home
        </a>
        <a th:href="@{/{userName}/menu(userName=${#authentication.name})}" sec:authorize="isAuthenticated()">
          Menu
        </a>
        <a href="/login" sec:authorize="!isAuthenticated()">
          Login
        </a>
        <a href="/signup" sec:authorize="!isAuthenticated()">
          SignUp
        </a>
        <a href="/logout" th:fragment="logout" sec:authorize="isAuthenticated()">
          Logout
        </a>
      </div>
    </header>
  </body>
</html>
```
  
これでMenuのリンクをクリックした際、URIに`認証情報.ユーザ名`を含めて遷移することができました。
また、認証成功時のリダイレクト先にもユーザ名を含めたい場合ですが、私は下記の方法を使いました。

### 認証成功時のハンドラ

```java:AuthenticationSuccessHandlerImpl.java
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {

    /**
     * 認証成功時の処理
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
        
        // 認証情報からユーザ名を抜き出し、遷移先のURI情報として使用。
        User userDetail = (User)authentication.getPrincipal();
        String redirectLocation = userDetail.getUserName() + "/menu";
        
        redirectStrategy.sendRedirect(request, response, redirectLocation);
    }

}
```
### Spring Securityの設定

```java:SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ~省略~

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // アクセス設定
        http.authorizeRequests().antMatchers("/", "/login", "/signup").permitAll()
                .antMatchers("/**").hasRole("USER");

        // ログインの設定
        http.formLogin().loginPage("/login")
                .usernameParameter("userId").passwordParameter("password") 
                .failureHandler(new hogeFailureHandler())
                 // 認証成功時のハンドラ
                .successHandler(new AuthenticationSuccessHandlerImpl())
                .and();

        // ログアウトの設定
        http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout**"))
                .logoutSuccessUrl("/");
    }

// ~省略~
}
```

# まとめ
無事、URIにユーザIDを含めることができるようになりました。
**セキュリティ的に問題があるよー**
とか
**もっと良いやり方があるよー**
って人はご指摘頂けるとありがたいです。