[Security] Spring Security 인증 처리 과정

2022. 12. 1. 00:12

Spring Security 인증 처리 과정

https://www.javainuse.com/webseries/spring-security-jwt/chap3

- 로그인 요청

- 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를 조회하고 인증에 성공하면 반환

BELATED ARTICLES

more