[Security] Spring Security 인증 처리 과정
Spring Security 인증 처리 과정
- 로그인 요청
- AuthenticationFilter에서 username과 password로 Authentication(인증되지 않은) 객체 생성
- Authentication 객체를 AuthenticationManager에 전달
AuthenticationManager는 인증 처리 역할을 하는 인터페이스, 인증을 위한 실질적인 관리는 AuthenticationManager를 구현하는 클래스 (ProviderManager) 를 통해 이루어짐
- ProviderManager로부터 전달받은 Authentication을 AuthenticationProvider는 UserDetailsService를 이용해 UserDetails 조회
- AuthenticationProvider는 전달받은 UserDetails를 이용해서 검증하고 검증에 성공하면 인증된 Authentication 생성
- AuthenticationFilter까지 전달된 Authentication은 SecurityContext에 저장
인증 컴포넌트
1. UsernamePasswordAuthenticationFilter
request를 제일 먼저 만나는 컴포넌트, AbstractAuthenticationProcessingFilter 상속
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
//...
public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
attemptAuthentication 메서드에서 usernamePasswordAuthenticationToken 생성 (인증에 사용되는 토큰)
토큰을 생성하고 AuthenticationManager의 authenticate 메서드를 호출해서 인증 처리 위임
2. AuthenticationManager
인증 처리를 총괄하는 인터페이스
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
3. ProviderManager
적절한 AuthenticationProvider를 찾아서 AuthenticationProvider에 인증 처리 위임
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
//...
public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) {
Assert.notNull(providers, "providers list cannot be null");
this.providers = providers;
this.parent = parent;
checkState();
}
//...
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
int currentPosition = 0;
int size = this.providers.size();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
if (result == null && this.parent != null) {
try {
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
catch (ProviderNotFoundException ex) {
}
catch (AuthenticationException ex) {
parentException = ex;
lastException = ex;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
((CredentialsContainer) result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
//...
}
//...
}
4. AuthenticationProvider
UserDetailsService로부터 전달받은 UserDetails를 이용해서 인증 처리
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
//...
@Override
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
//...
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
//...
}
additionalAuthenticationChecks 메서드에서 password 검증
retrieveUser 메서드는 UserDetails를 조회하고 인증에 성공하면 반환
'Programming > Security' 카테고리의 다른 글
[Security] JJWT 라이브러리 Jwts.builder().setSubject null 문제 (0) | 2023.01.09 |
---|