SpringSecurity DBアクセス処理

# はじめに
SpringSecurity その3です。今回はDB認証の機能を実装していきます。
完全に理解することができていない為、不備等あるかもしれませんがご了承ください。

# SpringSecurityシリーズ　項目表
|NO  |タイトル                    |
|----|--------------------------|
|その1|[SpringSecurity 導入から基本的な認証フロー](https://qiita.com/ryo0527/items/dbdbb2a476cc74e9116b)|
|その2|[SpringSecurity ログインページをカスタマイズする](https://qiita.com/ryo0527/items/2ee963cea31e82fd3dc5)|
|その3|[SpringSecurity DBアクセス処理](https://qiita.com/ryo0527/items/0b0b161530a2f0eb9e0c)|
|その4|[SpringSecurity 403エラーページをカスタマイズする](https://qiita.com/ryo0527/items/065c1a871c80e55d2935)|

前回の続きからとなる為、その1とその2を実装後にご覧ください。

# 実装
### データベースの作成
PostgreSQLを使って、ログイン機能を実装していきます。
DB名を`security`とし、以下のテーブル構成とします。
```
CREATE TABLE users (
  id SERIAL NOT NULL,
  name TEXT NOT NULL,
  password TEXT NOT NULL,
  role TEXT NOT NULL,
  created_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY(id)
);
```

### テストデータの登録
ログインに使用するテスト用アカウントを作成します。
パスワードはBCrypt(ビー・クリプト)で暗号化して登録します。
`2a$08$MtiHnF8YzT80cyhgE9ZC.uCkGz.bHPH5Csq0yXtQrXvgZbPAgdwfS` = `user`　
`$2a$08$LLlfr54ItEpsjG5o7hySeelDms2.LZOHiEvA6QOI9Irt2t9LjbkH2` = `admin`
```
INSERT INTO users
  (name,password,role)
VALUES 
  ('user', '$2a$08$MtiHnF8YzT80cyhgE9ZC.uCkGz.bHPH5Csq0yXtQrXvgZbPAgdwfS', 'USER'),
  ('admin', '$2a$08$LLlfr54ItEpsjG5o7hySeelDms2.LZOHiEvA6QOI9Irt2t9LjbkH2', 'ADMIN');
```

### 依存関係の追加
DB認証を行うために、依存関係を追加します。
```:pom.xml
<!-- 中略 -->

<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.18.12</version>
	<scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- 中略 -->
```

### 接続設定の追加
DBにアクセスするために、接続設定を行います。
```:application.properties
spring.jpa.database=POSTGRESQL
spring.datasource.url=jdbc:postgresql://localhost:5432/{データベース名}
spring.datasource.username={POSTGRESQLのユーザー名}
spring.datasource.password={POSTGRESQLのパスワード}
spring.mvc.hiddenmethod.filter.enabled=true
```

### DB認証処理に使用するEntityとRepositoryを用意
DB認証を行うために、必要最小限のEntityとRepositoryを用意します。

#### Entity
```java:User.java
@Data
@Table(name = "users")
@Entity
public class User {
  @Id
  @Column
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private int id;

  @Column
  private String name;

  @Column
  private String password;

  @Column
  private String role;
}
```

#### Repository
```java:UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
  @Query("select t from User t WHERE t.name = ?1")
  User findUser(String name);
}
```

### SpringSecurityの設定
DB認証を行うためにSpringSecurityの設定をカスタマイズして行きます。

#### WebSecurityConfigurerAdapter
```java:SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private LoginUserDetailsService loginUserDetailsService;

  <!-- 中略 -->

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 認証するユーザー情報をデータベースからロードする
    auth.userDetailsService(loginUserDetailsService).passwordEncoder(passwordEncoder());
  }
}

```

#### AuthenticationProvider
認証時に呼び出される。<br　/>権限の付与等は`UserDetails`で行う。

`authenticate()メソッド` :	認証処理を実装するメソッド
`supports()メソッド` :	この認証プロバイダがサポートする
```java:AuthenticationProviderImpl.java
@Component
public class AuthenticationProviderImpl implements AuthenticationProvider {

  @Autowired
  LoginUserDetailsService loginUserDetailsService;

  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationCredentialsNotFoundException {
    authentication.isAuthenticated(); // この時点では false

    // 入力されたユーザー名とパスワードを取得
    String username = authentication.getName();
    String password = authentication.getCredentials().toString();

    // userの検索
    UserDetails user = loginUserDetailsService.loadUserByUsername(username);

    if (!user.getPassword().equals(password)) {
      throw new AuthenticationCredentialsNotFoundException("ユーザーIDまたはパスワードが誤っています");
    }

    // 入力されたユーザー名とパスワードを保持するAuthenticationを作成
    UsernamePasswordAuthenticationToken authenticationResult = new UsernamePasswordAuthenticationToken(user,
        authentication.getCredentials(), user.getAuthorities());

    // 認証情報にUserDetailsをセットする
    authenticationResult.setDetails(authentication.getDetails());
    authenticationResult.isAuthenticated(); // この時点では true
    return authenticationResult;
  }

  @Override
  public boolean supports(Class<?> authentication) {
　    // POST で送信されたユーザー名とパスワードで認証を行う
    return UsernamePasswordAuthenticationToken.class
        .isAssignableFrom(authentication);
  }
}
```

#### UserDetailsService
ログインページで入力されたユーザー名をキーにDBからユーザー情報を取得する。
ユーザーが存在しない場合、エラーメッセージが表示され認証に失敗する。
```java:LoginUserDetailsService.java
@Service
public class LoginUserDetailsService implements UserDetailsService {

  @Autowired
  UserRepository userRepository;

  @Override
  public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
    // 入力された名前をキーに「usersテーブル」のレコードを1件取得
    User user = userRepository.findUser(name);

    //　取得できなかった場合はExceptionを投げる
    if (user == null) {
      throw new AuthenticationCredentialsNotFoundException("ユーザーIDまたはパスワードが誤っています");
    }

    return new LoginUserDetails(user);
  }
}
```

### User
SpringSecurityのUserクラスを継承する。 ※EntityのUserクラスではない
権限などを付与し、認証処理を行う。
```java:LoginUserDetails.java
@Data
@EqualsAndHashCode(callSuper = false)
public class LoginUserDetails extends User {
  private final com.example.demo.entity.User user;

  // 認証処理
  public LoginUserDetails(com.example.demo.entity.User user) {
    super(user.getName(), user.getPassword(), AuthorityUtils.createAuthorityList("ROLE_" + user.getRole()));
    this.user = user;
  }

  public com.example.demo.entity.User getUserName() {
    return this.user;
  }
}
```
# おわりに
これでDB認証機能を実装することができました。
間違っている箇所ありましたら、ご指摘いただければ幸いです。
まだまだ課題や追加機能があるため、時間のある際に更新していきたいと思います。