Spring Security에서 AuthenticationProvider는 사용자 인증을 처리하는 핵심 인터페이스 중 하나입니다. 사용자의 자격 증명을 검사하고 인증이 성공하면 Authentication 객체를 반환합니다.
즉, 사용자의 username/password를 확인하거나, OAuth, JWT, API Key 등의 인증 방식을 처리하는 역할을 수행합니다.
1. AuthenticationProvider의 역할
- Authentication 객체를 입력받아 인증을 수행.
- 인증이 성공하면 Authenticated 상태의 Authentication 객체를 반환.
- 인증 실패 시 예외(BadCredentialsException 등)를 발생.
- 특정한 타입의 인증 객체만 처리할 수 있도록 제어 가능.
2. AuthenticationProvider 동작 흐름
Spring Security의 인증 흐름에서 AuthenticationProvider가 어떻게 동작하는지 자세히 살펴보겠습니다.
(1) 사용자가 로그인 요청을 보냄
사용자가 /login 엔드포인트를 통해 username과 password를 전송합니다.
(2) AuthenticationManager가 요청을 받음
AuthenticationManager는 AuthenticationProvider를 사용하여 인증을 수행합니다.
(3) AuthenticationProvider가 인증을 수행
- supports() 메서드로 입력된 Authentication 타입을 확인.
- authenticate() 메서드에서 인증 절차 수행.
(4) 인증이 성공하면 인증된 Authentication 객체를 반환
- 사용자 정보를 포함하는 UsernamePasswordAuthenticationToken 객체를 반환.
- 이후 SecurityContextHolder에 저장되어 요청이 인증된 상태가 됨.
(5) 인증이 실패하면 예외 발생
- 예를 들어, 비밀번호가 틀리면 BadCredentialsException 발생.
3. AuthenticationProvider 구현 예제
커스텀 인증 제공자를 만들어 Spring Security에서 사용해 보겠습니다.
(1) CustomAuthenticationProvider 구현
package com.example.security.auth;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Collections;
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final CustomUserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
public CustomAuthenticationProvider(CustomUserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
this.userDetailsService = userDetailsService;
this.passwordEncoder = passwordEncoder;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
// 사용자 정보 조회
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 비밀번호 검증
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
throw new BadCredentialsException("Invalid credentials");
}
// 인증 성공 시 Authentication 객체 반환
return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
(2) UserDetailsService 구현
package com.example.security.auth;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final Map<String, String> users = new HashMap<>();
public CustomUserDetailsService() {
// 임시 사용자 정보 (실제 환경에서는 DB 사용)
users.put("user", "$2a$10$somethinghashedpassword"); // BCrypt 암호화된 비밀번호
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (!users.containsKey(username)) {
throw new UsernameNotFoundException("User not found");
}
return User.withUsername(username)
.password(users.get(username))
.roles("USER")
.build();
}
}
(3) SecurityConfig 설정
package com.example.security.config;
import com.example.security.auth.CustomAuthenticationProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.List;
@Configuration
public class SecurityConfig {
private final CustomAuthenticationProvider authenticationProvider;
public SecurityConfig(CustomAuthenticationProvider authenticationProvider) {
this.authenticationProvider = authenticationProvider;
}
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(List.of(authenticationProvider));
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4. 전체적인 동작 흐름 요약
- 사용자가 /login 요청을 보내 username/password를 전송.
- AuthenticationManager가 CustomAuthenticationProvider를 호출.
- CustomAuthenticationProvider가 UserDetailsService에서 사용자 정보를 조회.
- 비밀번호를 검증하고 인증 성공 시 Authentication 객체 반환.
- SecurityContextHolder에 인증 정보를 저장하여 인증된 요청으로 처리.
5. AuthenticationProvider 커스텀 활용 예시
- JWT 인증: DB 대신 JWT 토큰을 기반으로 인증 처리.
- OAuth2 인증: Google, Facebook 등의 OAuth2 기반 인증.
- API Key 인증: API Key를 헤더로 전달받아 인증.
6. 결론
Spring Security의 AuthenticationProvider는 인증 로직을 직접 구현할 수 있는 강력한 확장 포인트입니다. 기본 DaoAuthenticationProvider를 사용할 수도 있지만, 특정 요구 사항에 맞춰 커스텀 AuthenticationProvider를 구현하면 더욱 유연한 인증 방식을 적용할 수 있습니다.
'Spring > Spring Securitiy' 카테고리의 다른 글
10. AuthenticationManager 개요 (0) | 2025.02.24 |
---|---|
7. 6.1버전 전에 구현되었던 SecurityConfig 리펙토링 (0) | 2025.02.14 |
6. 인증(Authentication)의 기본 개념과 구조와 Spring Security (0) | 2025.01.23 |
5. Spring Security 6 버전 기준 이전 방식(WebSecurityConfigurerAdapter 사용)과 최신 방식(SecurityFilterChain 사용) 비교 (0) | 2025.01.23 |
4. SecurityConfig 기본 사항 (0) | 2025.01.22 |