SpringBoot基礎 ログイン認証のカスタマイズ

## ログインユーザ情報の構成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1035894/30b22f52-08e8-0fa4-cd44-d8dff1660751.png)

|||
|---|---|
|SecurityContextHolder|SecurityContextを保持する|
|SecurityContext|セキュリティに関わる情報を保持する|
|Authentication|認証トークンを保持する|
|Principal|ログインユーザ情報を保持する|
|Credentials|ユーザID、パスワード等を保持する|
|Authorities|権限情報を保持する|

## 基本的な認証に必要な設定あれこれ

### SecurityConfigファイル

```java:SecurityConfigファイルのサンプル
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    // 認証項目カスタマイズ時は指定
    @Autowired
    @Qualifier("UserDetailsServiceImpl")
    private UserDetailService userDetailsService

    // 独自のログイン認証を実装する場合は指定
    @Autowired
    @Qualifier("TestAuthenticationProvider")
    private AuthenticationProvider authenticationProvider 
   
    @Override
    protected void configure(HttpSecurity http) throws Exception{
        //ログイン認証不要なページ、直リンク禁止ページを設定
        http
          .authorizeRequests()
            //ログイン無しでアクセスOK
            .antMatchers("/login").permitAll()
            //上記以外はログインが必要
            .anyRequest().authenticated();
        //ログイン処理
        http
          .formLogin()
            //ログイン処理時のパス
            .loginProcessingUrl("/login")
            //ログインページの指定
            .loginPage("/login")
            //ログイン失敗時の遷移先
            .failreUrl("/login?error")
            //ユーザIDパラメータ名 ※login.htmlのnameに指定
            .usernameParameter("userId")
            //パスワードパラメータ名 ※login.htmlのnameに指定
            .passwordParameter("password")
            //ログイン後の遷移先
            .defaultSuccessUrl("/home")

            //(カスタマイズ)ログイン後の遷移先を制御　※別途クラス作成が必要
            .successHandler(successHandler)

        //ログアウト処理
        http
          .logout()
            //ログアウト処理時のパス
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            //ログアウトページの指定
            .logoutUrl("/logout")
            //ログアウト後の遷移先
            .logoutSuccessUrl("/login");
    }
    
    // 認証項目カスタマイズ時は指定
    @Orverride
    protected void configure(Authentication ManagerBuilder auth) throws Exception{
        // 1.独自のログイン認証を実装しない場合且つ、ログイン処理時のユーザ情報をDBから取得する場合
        auth.userDetailService(userDetailsService).passwordEncoder(passwordEncoder());

        // 2.独自のログイン認証を実装する場合
        auth.authenticationProvider(authenticationProvider)

    }
}
```


### ログイン画面のコントローラー

```java:コントローラークラスのサンプル
@Controller
public class LoginController{
    @GetMapping("/login")
    public String init(){
        return "login";
    }
}
```

### ログイン後遷移先のコントローラー

```java:コントローラークラスのサンプル①
@Controller
public class HomeController{

    @GetMapping("/home")
    public String init(Model model, Principal principal){

        // SecurityContextHolderからユーザ情報を取得する
        Authentication authentication = (Authentication) principal;
        User user = (User) authentication.getprincipal();

        /** ログイン後の任意処理 */
        // user.toString()でユーザ情報を取得可能
        
        return "home";
    }
}
```

@AuthenticationPrincipalを使用することでUser情報取得を省略可能

```java:コントローラークラスのサンプル②
@Controller
public class HomeController{

    // ※
    @GetMapping("/home")
    public String init(Model model, @AuthenticationPrincipal User user){

        /** ログイン後の任意処理 */
        // user.toString()でユーザ情報を取得可能

        return "home";
    }
}
```
:::note
指定しているUserクラスはSpringのクラス。カスタマイズする場合は変更が必要
:::

### ログイン後のhtml画面

sec:authentication属性で、ユーザIDなら「name」、ロールなら「principal.authorities」でユーザ情報を取得可能
```html:home.html

//省略

// ユーザIDの取得
<p>ユーザID：<span sec:authentication="name"></span>

// ロールの取得
<p>ロール：<span sec:authentication="principal.authorities"></span>

// 認証項目カスタマイズ時は指定
<p>ユーザID：<span sec:authentication="principal.username"></span> 
<p>任意項目：<span sec:authentication="principal.[userDetailsの実装クラスに指定したフィールド]"></span>

//省略


```

## 独自ユーザクラスの作成

### UserDetailsクラス

DBからユーザを取得する際は下記のクラスを使用

|||
|---|---|
|DaoAuthenticationProvider|DBからユーザを取得して認証処理を行うクラス。Authentication、Principalにユーザ情報を設定する。パスワードチェックもこのクラスが提供する。|
|UserDetailsService|ユーザIDからユーザ情報を取得するインターフェース。このクラスを実装したクラスでDB取得処理を実装し、独自ユーザクラスを生成する。|
|UserDetails|ユーザクラス用のインターフェース。このクラスを実装したクラスが独自ユーザクラスとなる。|

:::note
UserDetaiols：userNameフィールドはSpringのデフォルトでユーザIDとしてみなされるため注意
:::


## 独自のログイン認証を実装

通常、ログインIDとパスワードで認証をおこなうところ、テナントIDなども認証項目として利用したいケース。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1035894/b3d17f24-53b1-571e-386b-55f584fb8b64.png)

|||
|---|---|
|AuthenticationFilter|URLリクエストに対し、Authentication（認証情報）を作成。それをAuthenticationManagerに渡して、認証処理を呼び出す。|
|AuthenticationManager|AuthenticationProviderを管理。認証処理自体はAuthenticationProviderで実行される。複数のAuthenticationProviderを順番に呼び出し、一つでも認証成功となれば認証OKとなる。|
|AuthenticationProvider|認証処理を実装する。|

```java:AuthenticationProviderのサンプル
@Component("TestAuthenticationProvider")
public class TestAuthenticationProvider implements AuthenticaitonProvider {
    /** 認証処理を実装 */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        
        // 独自の認証処理を実装
        
        // UsernamePasswordAuthenticationTokenには権限のsetterがないため、新たなインスタンスを生成する。
        return new UsernamePasswordAuthenticationToken(principal, credentials, authorities)
        
    }

    /** 認証対象のクラスを制限するためのメソッド。
     *   今回は[UsernamePasswordAuthenticationToken]だけを認証対象にしている
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAddignableForm(authenticaiton);
    }
}
```