1. Security のコア概念
Spring Security の本質は フィルターチェーン(Filter Chain) であり、内部には異なる機能を提供する複数のフィルターが含まれています。基本的なアプリケーションにおいて、フィルターチェーンの主要なフィルターは以下のとおりです:
- UsernamePasswordAuthenticationFilter:ユーザー名とパスワードに基づくログインリクエストを処理し、ユーザー認証を担当する。
-
ExceptionTranslationFilter:フィルターチェーン内で発生した
AccessDeniedException
やAuthenticationException
をキャッチし、適切に処理する。 - FilterSecurityInterceptor:アクセス制御の決定を担当し、ユーザーが保護されたリソースにアクセスする権限を持っているかどうかを検証する。
Spring Security のフィルターチェーンの具体的な構成や実行順序は、デバッグモードで確認できます。
2. Security 認証プロセス
2.1 Security 設定クラス
Spring Security 5.7 以降では、WebSecurityConfigurerAdapter
を継承するのではなく、SecurityFilterChain
を使用してセキュリティポリシーを設定することが推奨されています。
@Configuration
public class WebSecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
// ユーザーオブジェクトを作成
UserDetails user = User.withUsername("Tanaka")
.password("{noop}111111") // `{noop}` は暗号化なし
.roles("USER")
.build();
// InMemoryUserDetailsManager を作成し、ユーザー情報を登録
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(user);
// UserDetailsService を返し、Spring Security に管理させる
return userDetailsManager;
}
}
UserDetailService
インターフェースに入ると、重要なメソッドがあり、次に使用されます
2.2 認証プロセス(Authentication)
1. ユーザーがログインリクエストを送信
ユーザーがフォームログインを行う際、Spring Security に組み込まれている UsernamePasswordAuthenticationFilter
フィルターが POST /login
リクエストをキャッチし、attemptAuthentication
メソッドを呼び出します。
次に AbstractAuthenticationProcessingFilter
メソッドに進みます。
このフィルターが実行され、attemptAuthentication
メソッドに入ります。
2. 認証オブジェクトの作成
UsernamePasswordAuthenticationFilter
はリクエストパラメータを解析し、UsernamePasswordAuthenticationToken
を使用してユーザーの認証情報(ユーザー名とパスワード)をカプセル化します。
Authentication authentication = new UsernamePasswordAuthenticationToken(username, password);
3. AuthenticationManager
による認証処理
AuthenticationManager
(デフォルト実装は ProviderManager
)は Authentication
オブジェクトを受け取り、AuthenticationProvider
に委託して認証を行います。
Authentication authenticationResult = authenticationManager.authenticate(authentication);
4. AuthenticationProvider
による認証処理
ProviderManager
は内部で複数の AuthenticationProvider
を管理しており、デフォルトでは DaoAuthenticationProvider
が使用されます。
5. ユーザー情報のロード(UserDetailsService)
DaoAuthenticationProvider
は UserDetailsService#loadUserByUsername()
メソッドを呼び出し、メモリやデータベースからユーザー情報をロードします。
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
return this.userDetailsService.loadUserByUsername(username);
}
6. パスワードの検証
DaoAuthenticationProvider
は PasswordEncoder#matches()
メソッドを使用して、入力されたパスワードと保存されているパスワードを比較します。
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) {
if (!passwordEncoder.matches(authentication.getCredentials().toString(), userDetails.getPassword())) {
throw new BadCredentialsException("Invalid password");
}
}
7. 認証結果の処理
-
認証成功:
-
Authentication
オブジェクトが作成され、SecurityContextHolder
に設定される。 -
AuthenticationSuccessHandler
がトリガーされ、(デフォルトでは元のアクセスページまたはホームページにリダイレクト)。
認証失敗:
-
AuthenticationFailureHandler
がトリガーされ、認証失敗のレスポンスを返す。
-
if (authentication.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}