前回、Spring Securityの基本導入と、アクセスルールの設定方法(認可)を学びました。しかし、設定ファイルを書いただけでは、実際にユーザーをログインさせる「認証」の仕組みは動きません。
今回は、Spring SecurityがユーザーのIDとパスワードを検証し、認証を完了させるために必須となる2つのコンポーネントについて、具体的な実装方法を学びます。
- UserDetailsService: ログインIDからユーザー情報を取得する(認証データプロバイダ)。
- PasswordEncoder: パスワードを安全にハッシュ化し、照合する(パスワードエンコーダー)。
1. ユーザー情報プロバイダ:UserDetailsService
Spring Securityは、ユーザーがログインフォームに入力したIDを受け取ると、そのIDに紐づくユーザー情報(ID、ハッシュ化されたパスワード、権限など)をDBから取得するために、UserDetailsServiceというインターフェースの実装クラスを呼び出します。
1-1. UserDetailsとUserDetailsService
- UserDetailsService: ユーザー名を受け取り、UserDetailsオブジェクトを返すサービス(インターフェース)。
- UserDetails: Spring Securityが認証・認可のために必要とするユーザー情報の入れ物(インターフェース)。DBの User Entityを直接返すのではなく、このインターフェースを実装したクラスを使います。
1-2. 実装例(UserService)
ここでは、既存のService層に UserDetailsService を実装する形で進めます。
Java
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
// UserDetailsServiceを実装
@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService {
private final UserRepository userRepository; // ユーザー情報をDBから取得するRepository
// ユーザー名(ログインID)を受け取り、UserDetailsを返すメソッド
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// ① UserRepositoryを使ってDBからユーザー情報を取得
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("ユーザー名 " + username + " は見つかりません。"));
// ② 取得したUser Entityから、UserDetailsオブジェクトを作成して返す
return org.springframework.security.core.userdetails.User.builder()
// ユーザー名
.username(user.getUsername())
// ハッシュ化されたパスワード(後述)
.password(user.getPassword())
// ユーザーが持つ権限(Role)
.roles("USER")
.build();
}
}
これで、Spring SecurityはログインIDを受け取ると、この UserService を通じてDBからユーザー情報(特にハッシュ化されたパスワード)を取得できるようになります。
2. パスワードの安全な取り扱い:PasswordEncoder
セキュリティにおいて、パスワードを平文(暗号化されていないそのままの文字列)でデータベースに保存したり、ネットワークでやり取りしたりするのは絶対禁止です。
Spring Securityは、パスワードの安全な保管と照合のために、PasswordEncoderというインターフェースを定義しています。
2-1. ハッシュ化の原則
パスワードは、ハッシュ関数(例: BCrypt)を使って不可逆な文字列(ハッシュ値)に変換してDBに保存します。
- 保存時: ユーザーが登録時に入力したパスワード $\to$ ハッシュ化 $\to$ DBに保存
- 認証時: ユーザーがログイン時に入力したパスワード $\to$ ハッシュ化 $\to$ DBのハッシュ値と比較(一致すればOK)
2-2. PasswordEncoderのBean定義(BCryptPasswordEncoder)
Spring Securityでは、BCryptPasswordEncoder が推奨されています。このオブジェクトをBeanとして登録するだけで、Spring Securityが自動で利用してくれます。
Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig {
// ... (前回の SecurityFilterChain の定義など)
// PasswordEncoder Beanの定義
@Bean
public PasswordEncoder passwordEncoder() {
// BCryptアルゴリズムを使用
return new BCryptPasswordEncoder();
}
}
この PasswordEncoder のBeanがコンテナに存在することで、Spring Securityはログイン時に以下の処理を自動で行います。
- ユーザーの入力パスワードをBCryptでハッシュ化。
- UserDetailsService から取得したDB保存済みのハッシュ値と照合。
- 一致すれば認証成功、失敗すれば認証失敗(例外スロー)。
3. DBへのパスワードの保存
上記の設定を有効にするには、DBに保存されているパスワードがBCryptでハッシュ化されている必要があります。
ユーザー登録のServiceメソッドなどで、パスワードを保存する前に必ず PasswordEncoder を使ってハッシュ化する処理を追加します。
Java
@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService {
// PasswordEncoderもDIで注入する
private final PasswordEncoder passwordEncoder;
// 新規ユーザー登録メソッド
@Transactional
public User registerUser(String username, String rawPassword) {
// パスワードをハッシュ化して保存する
String encodedPassword = passwordEncoder.encode(rawPassword);
User newUser = new User();
newUser.setUsername(username);
// ハッシュ化されたパスワードをセット
newUser.setPassword(encodedPassword);
return userRepository.save(newUser);
}
}
✅ 本日のまとめ
- UserDetailsServiceは、ログインIDを受け取り、認証に必要なユーザー情報(UserDetails)をDBなどから取得する役割を担う。
- PasswordEncoderは、パスワードを安全にハッシュ化し、ログイン時に入力されたパスワードとDBのハッシュ値を照合する役割を担う。
BCryptPasswordEncoderをBeanとして登録するのが、現在の標準的な実装である。- DBに保存するユーザーのパスワードは、必ず PasswordEncoder を使ってハッシュ化する必要がある。
🔔 次回予告
これで、ユーザーがログインフォームから認証する仕組みが完成しました。しかし、セキュリティ対策はこれだけでは不十分です。
次回は、認証後のセキュリティ、特にWebアプリケーションの古典的な脅威である**CSRF(クロスサイトリクエストフォージェリ)やXSS(クロスサイトスクリプティング)**といった脆弱性への対策について学びます。
次回:【第30回】Webアプリケーションの主要な脅威とセキュリティ対策 にご期待ください!


コメント