💡 오늘 학습 키워드
- AuthenticationManager
- AuthenticationProvider
🎯 학습 내용 정리
Spring Security에서 로그인 기능을 구현하다 보면 AuthenticationManager, AuthenticationProvider, UserDetailsService를 설정하게 된다. 하지만 대부분의 개발자는 AuthenticationManager.authenticate()만 호출한 채 내부에서 어떤 객체가 인증을 수행하는지까지는 살펴보지 않는 경우가 많다.
실제로 AuthenticationManager는 직접 인증을 수행하지 않는다. 인증을 수행할 수 있는 여러 AuthenticationProvider에게 인증을 위임하고, 적절한 Provider가 사용자의 자격 증명을 검증한 뒤 인증이 완료된 Authentication 객체를 반환한다.
1. AuthenticationManager
로그인 요청은 어디로 전달될까?
사용자가 로그인을 요청하면 가장 먼저 UsernamePasswordAuthenticationFilter가 요청을 처리한다.
예를 들어 다음과 같은 로그인 요청이 들어온다고 가정해 보자.
POST /login
Content-Type: application/json
{
"username": "spring",
"password": "1234"
}
Filter는 요청에서 아이디와 비밀번호를 추출하여 인증 전(Authentication Request) 객체를 생성한다.
UsernamePasswordAuthenticationToken authentication =
UsernamePasswordAuthenticationToken.unauthenticated(
username,
password
);
이 객체는 아직 인증되지 않은 상태이며, 단순히 사용자가 입력한 자격 증명만 가지고 있다.
이후 Filter는 AuthenticationManager에게 인증을 요청한다.
Authentication result =
authenticationManager.authenticate(authentication);
AuthenticationManager는 인증을 수행하지 않는다.
클래스 이름 때문에 AuthenticationManager가 직접 인증을 수행한다고 생각하기 쉽다.
하지만 실제 역할은 인증을 적절한 Provider에게 위임하는 것이다.
Spring Security의 기본 구현체는 ProviderManager이다.
구조는 다음과 같다.
AuthenticationManager
↓
ProviderManager
↓
AuthenticationProvider
즉, 우리가 사용하는 AuthenticationManager는 대부분 ProviderManager이며, 실제 인증은 AuthenticationProvider가 수행한다.
2. AuthenticationProvider
ProviderManager는 AuthenticationProvider를 어떻게 선택할까?
ProviderManager에는 여러 AuthenticationProvider가 등록될 수 있다.
예를 들어 다음과 같은 Provider가 있다고 가정한다.
DaoAuthenticationProvider
JwtAuthenticationProvider
OAuth2AuthenticationProvider
ProviderManager는 모든 Provider를 순회하면서 현재 Authentication 객체를 처리할 수 있는 Provider를 찾는다.
이때 사용하는 메서드가 supports()이다.
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication);
}
supports()가 true를 반환한 Provider만 실제 인증을 수행한다.
DaoAuthenticationProvider는 무엇을 할까?
Spring Security에서 가장 많이 사용하는 Provider가 DaoAuthenticationProvider이다.
인증 과정은 다음과 같다.
Authentication
↓
UserDetailsService
↓
UserDetails
↓
PasswordEncoder.matches()
↓
Authentication 생성
이 과정에서 가장 먼저 사용자를 조회한다.
UserDetails user =
userDetailsService.loadUserByUsername(username);
조회된 UserDetails의 비밀번호와 사용자가 입력한 비밀번호를 비교한다.
passwordEncoder.matches(
rawPassword,
user.getPassword()
);
비밀번호가 일치하면 인증이 완료된다.
인증이 성공하면 무엇이 반환될까?
비밀번호 검증이 완료되면 인증된 Authentication 객체를 생성한다.
UsernamePasswordAuthenticationToken authenticated =
UsernamePasswordAuthenticationToken.authenticated(
userDetails,
null,
userDetails.getAuthorities()
);
이 객체는 로그인 성공 이후 사용되는 Authentication이다.
인증 전과 인증 후의 차이는 다음과 같다.
| 구분 | 인증 전 | 인증 후 |
| Principal | username | UserDetails |
| Credentials | password | null |
| Authorities | 없음 | ROLE_USER |
| authenticated | false | true |
인증이 실패하면 어떻게 될까?
인증에 실패하면 AuthenticationException 계열 예외가 발생한다.
대표적인 예외는 다음과 같다.
| 예외발생 | 상황 |
| UsernameNotFoundException | 사용자 없음 |
| BadCredentialsException | 비밀번호 불일치 |
| LockedException | 계정 잠김 |
| DisabledException | 계정 비활성화 |
| CredentialsExpiredException | 비밀번호 만료 |
예를 들어 비밀번호가 틀린 경우 다음과 같은 예외가 발생한다.
throw new BadCredentialsException(
"Bad credentials"
);
이 예외는 이후 ExceptionTranslationFilter에서 처리되어 적절한 응답으로 변환된다.
AuthenticationManager는 직접 구현해야 할까?
대부분의 프로젝트에서는 직접 구현할 필요가 없다.
Spring Boot는 AuthenticationManager를 Bean으로 제공한다.
@Bean
AuthenticationManager authenticationManager(
AuthenticationConfiguration configuration
) throws Exception {
return configuration.getAuthenticationManager();
}
로그인 서비스에서는 다음과 같이 사용할 수 있다.
@Service
@RequiredArgsConstructor
public class AuthService {
private final AuthenticationManager authenticationManager;
public void login(
String username,
String password
) {
Authentication authentication =
UsernamePasswordAuthenticationToken
.unauthenticated(
username,
password
);
authenticationManager.authenticate(authentication);
}
}
직접 사용자 조회와 비밀번호 비교를 수행하는 대신 Spring Security에게 인증을 위임하는 것이 권장된다.
JWT 로그인에서는 어떻게 활용될까?
JWT 기반 로그인도 최초 로그인 과정은 동일하다.
Login Request
↓
AuthenticationManager
↓
DaoAuthenticationProvider
↓
PasswordEncoder
↓
Authentication
↓
JWT 생성
인증이 성공한 이후에만 JWT를 발급한다.
Authentication authentication =
authenticationManager.authenticate(token);
CustomUserDetails principal =
(CustomUserDetails) authentication.getPrincipal();
String accessToken =
jwtProvider.createAccessToken(
principal.getUserId(),
principal.getAuthorities()
);
즉, JWT는 AuthenticationManager를 대체하는 것이 아니라, 인증 성공 이후 발급되는 결과물이다.
로그인 요청부터 인증 완료까지의 전체 흐름은 다음과 같다.
Client
↓
UsernamePasswordAuthenticationFilter
↓
AuthenticationManager
↓
ProviderManager
↓
AuthenticationProvider
↓
UserDetailsService
↓
UserDetails
↓
PasswordEncoder
↓
Authentication 생성
↓
SecurityContextHolder
↓
JWT 생성
↓
Response
실무에서 자주 하는 실수는 무엇이 있을까?
1. AuthenticationManager를 우회하는 경우
다음과 같이 Repository를 직접 조회하고 비밀번호를 비교하는 방식은 Spring Security의 인증 체계를 우회하게 된다.
User user = userRepository.findByUsername(username)
.orElseThrow();
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new IllegalArgumentException();
}
이 방식은 Spring Security가 제공하는 예외 처리와 인증 흐름을 활용하지 못한다.
인증은 AuthenticationManager에게 위임하는 것이 바람직하다.
2. PasswordEncoder로 직접 암호화 후 equals()를 사용하는 경우
다음과 같은 구현은 잘못된 방식이다.
passwordEncoder.encode(rawPassword)
.equals(encodedPassword);
BCryptPasswordEncoder는 같은 비밀번호라도 매번 다른 해시를 생성하기 때문에 equals() 비교는 항상 실패할 수 있다.
반드시 matches()를 사용해야 한다.
passwordEncoder.matches(
rawPassword,
encodedPassword
);
3. JWT 인증과 로그인 인증을 혼동하는 경우
AuthenticationManager는 로그인 시 사용자 자격 증명을 검증하는 역할을 수행한다.
반면 JWT Filter는 이미 발급된 토큰의 유효성을 검증하는 역할을 수행한다.
두 과정은 서로 대체 관계가 아니라 연속적인 인증 과정이다.
AuthenticationManager는 인증을 직접 수행하지 않고, ProviderManager를 통해 적절한 AuthenticationProvider에게 인증을 위임한다. DaoAuthenticationProvider는 UserDetailsService로 사용자를 조회하고 PasswordEncoder로 비밀번호를 검증한 뒤, 인증이 완료된 Authentication 객체를 생성한다.
JWT 기반 인증에서도 최초 로그인은 동일한 과정을 거치며, 인증이 성공한 이후에만 JWT가 발급된다. 따라서 AuthenticationManager와 AuthenticationProvider의 역할을 이해하면 Spring Security의 로그인 흐름을 더욱 명확하게 파악할 수 있다.
📚 한줄 정리
AuthenticationManager는 인증을 위임하고, AuthenticationProvicer는 사용자 조회와 비밀번호 검증을 통해 인증된 Authentication을 생성하는 실제 인증 수행자이다.
'Bootcamp > Implementation' 카테고리의 다른 글
| CORS와 CSRF (0) | 2026.07.14 |
|---|---|
| AuthenticationEntryPoint와 AccessDeniedHandler 예외 처리 (0) | 2026.07.13 |
| UserDetails와 UserDetailsService (0) | 2026.07.09 |
| Authentication과 SecurityContextHolder (0) | 2026.07.08 |
| JWT 기반 로그인 구현과 Access Token 발급 (0) | 2026.07.07 |