はじめに
SpringSecurity その3です。今回はDB認証の機能を実装していきます。
完全に理解することができていない為、不備等あるかもしれませんがご了承ください。
SpringSecurityシリーズ 項目表
NO | タイトル |
---|---|
その1 | SpringSecurity 導入から基本的な認証フロー |
その2 | SpringSecurity ログインページをカスタマイズする |
その3 | SpringSecurity DBアクセス処理 |
その4 | SpringSecurity 403エラーページをカスタマイズする |
前回の続きからとなる為、その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認証を行うために、依存関係を追加します。
<!-- 中略 -->
<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にアクセスするために、接続設定を行います。
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
@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
@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
@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
認証時に呼び出される。
権限の付与等はUserDetails
で行う。
authenticate()メソッド
: 認証処理を実装するメソッド
supports()メソッド
: この認証プロバイダがサポートする
@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からユーザー情報を取得する。
ユーザーが存在しない場合、エラーメッセージが表示され認証に失敗する。
@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クラスではない
権限などを付与し、認証処理を行う。
@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認証機能を実装することができました。
間違っている箇所ありましたら、ご指摘いただければ幸いです。
まだまだ課題や追加機能があるため、時間のある際に更新していきたいと思います。