[SpringSecurity] AuthenticationManger 과정 코드 뜯어보기 / 총정리

유알·2022년 12월 28일
1

[SpringSecurity]

목록 보기
6/15

AuthenticationManager가 어디에서 호출되는가? 🤔
이 질문은 추후에 작성된 나의 글을 참고 하라. 하지만 이 글을 먼저 읽는 것도 좋은 방법이다.

시작

아래의 코드를 작성하다가 AuthenticationManager가 뭔지 파헤쳐 보았다.

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private final AuthenticationManager authenticationManager;

    // /login 요청을 하면 로그인 시도를 위해서 실행되는 함수
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) 
    			throws AuthenticationException {
        try {
        	//Request 에서 JSON으로 보내준 정보를 User객체에 저장하는 코드
            ObjectMapper om = new ObjectMapper();
            User user = om.readValue(request.getInputStream(),User.class);
            
			//여기 부분에서 의문이 생겼다.
            UsernamePasswordAuthenticationToken authenticationToken =
            	new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
            Authentication authentication = authenticationManager.authenticate(authenticationToken);
            
            // ...

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

UsernamePasswordAuthenticationFilter를 구현해서 SecurityConfig에 직접 추가해주었다.
여기서 핵심인 부분은 아무래도 이 인증을 하는 부분

UsernamePasswordAuthenticationToken authenticationToken =
		new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
Authentication authentication = authenticationManager.authenticate(authenticationToken);

이 부분의 작동을 파해쳐 보았다.

등장 객체 설명

  1. AuthenticationManager
  2. ProviderManager
  3. AuthenticationProvider

AuthenticationManager

자 시작부분의 코드에서 우리는 authentication.authenticate(authenticationToken)을 통해 인증을 시도했다.
그렇다면 AuthenticationManager란 무엇일까?

public interface AuthenticationManager {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;

}

아주 간단한 인터페이스 이다. authenticate 라는 메서드 하나만 담고 있다.
즉 이 인터페이스의 구현체는 Authentication 요청을 처리한다.

ProviderManager (AuthenticationManager 구현체)

ProviderManager은 가장 흔한 AuthenticationManager의 구현체이다.
이 구현체의 역할은 아주 명확하다.
일명 적합한 인증 제공자 찾아주기

  • ProviderManagerAuthenticationProvider을 여러개를 가지고 있다.

    (AuthenticationProvider은 곧 나오겠지만, 각 타입별로 인증을 제공하는 객체이다.)
  • ProviderManager은 어떤 AuthenticationProvidernull이 아닌 return 을 제공 할 때까지 목록을 차례대로 반복한다.
  • AuthenticationProvidernull 이 아닌값을 반환했다는 것은 인증 요청을 결정할 수 있는 권한이 있으며 더이상 다음 AuthenticationProvider를 시도하지 않아도 됨을 의미합니다.
  • AuthenticationProvider가 요청을 성공적으로 인증하면, 이전 AuthenticationException이 무시되고 성공적인 인증이 사용된다.

간단하게 말하면 " 주어진 Authentication을 잘 처리할 수 있는 Provider가 나타날 때까지 쭉 훑어본다. " 라고 이해하면 된다.

참고로 이렇게 두개의 ProviderManager을 설정할 수도 있는데, 이는 SecurityFilterChanin을 두개 생성할 때 각 FilterChain에 서로 다른 ProviderManager을 적용하고 싶을 때 유용하다.
코드에서는 AuthenticationManager인터페이스로 받고 각각 구현된 ProviderManager를 배정하면 되기 때문이다.

AuthenticationProvider

타입별 인증제공자

public interface AuthenticationProvider {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;

	boolean supports(Class<?> authentication);

}

ProviderManager는 특정 타입에 대한 인증을 진행하는 역할을 하고, 두가지 메서드가 정의 되어있다. 위에서 말했듯 AuthenticationProvider는 루프를 돌며 자기가 갖고 있는 AuthenticationProvider들 하나씩 적합한지 체크한다.

authentication메서드는 말그대로 Authentication을 실행하는 로직이 담겨있다.
supports는 이 Provider가 해당 타입에 대한 인증을 진행하는지 boolean으로 알려준다.

간단한 동작 다이어그램

우리가 이번에 예시로 볼 AuthenticationProvider는 대표적인 DaoAuthenticationProvider이다.

코드 살펴보기

1. Authentication 만들어서 전달하기

UsernamePasswordAuthenticationToken authenticationToken =
	new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
Authentication authentication = authenticationManager.authenticate(authenticationToken);

Authentication 간단 설명

간단하게 설명하자면 Authentication에는 두가지 목적이 있다.

  • 유저가 전달한 credentials를 AuthenticationManager에 전달하기 위해 (이 경우 isAuthenticated()false를 반환한다.
  • Authenticated 된 유저를 나타낼때

Authentication은 다음 세가지를 포함한다.

  • principal : 유저 식별/구별하는 요소 , username/password에서는 UserDetail이 사용됨
  • credentials : 주로 비밀번호, 보통 인증이 완료되면 지워진다.
  • authorities : 권한

UsernamePasswordAuthenticationToken

UsernamePasswordAuthenticationTokenAuthentication의 자손이다.
Authentication -> AbstractAuthenticationToken -> UsernamePasswordAuthenticationToken

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
	// UsernamePasswordAuthenticationToken Constructor
    
	// 인증 안됬을 때 생성자
    // AuthenticationManager 로 전달할때 사용
	public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
		super(null);
		this.principal = principal;
		this.credentials = credentials;
		setAuthenticated(false);
	}
    // 인증 됬을 때 생성자
    // 인증 다 되면 만들어서 리턴해줌
	public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
			Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		this.credentials = credentials;
		super.setAuthenticated(true); // must use super, as we override
	}

authenticationManager.authenticate(authenticationToken);

이렇게 만들어진 전달용(아직 인증안된) AuthenticationauthenticationManager.authenticate()의 인자로 넘겨준다.

2. ProviderManager

ProviderManagerAuthenticationProvider의 대표적인 구현체 이다.
그 코드를 살펴보겠다.
1 번의 코드에서 생성된 Authentication(isAuthentication = false)을 이 클래스의 .authenticate()메서드의 인자로 넘겨주었다.

class

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {

생성자

	public ProviderManager(AuthenticationProvider... providers) {
		this(Arrays.asList(providers), null);
	}

	public ProviderManager(List<AuthenticationProvider> providers) {
		this(providers, null);
	}

	public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) {
		Assert.notNull(providers, "providers list cannot be null");
		this.providers = providers;
		this.parent = parent;
		checkState();
	}

인증을 진행하는 AuthenticationProvider을 받는 생성자들이고 특이하게 한개의 생성자는 부모 AuthenticationManager을 인자로 받는 생성자도 있다.
아래 로직에서 만약 이 ProviderManager에서 해결을 못했을 때, 부모 AuthenticationManager가 있다면 부모 AuthenticationManager을 한번 뒤져서 루프를 찾아보는 로직이 있다.

Provider :: authenticate 메서드

핵심 메서드이다. AuthenticationManager인터페이스에 명시되어있는 하나의 메서드 이기도 하다.
일단 전체코드를 보고 단계별로 설명을 하겠다.

@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) {
			// Allow the parent to try.
			try {
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException ex) {
				parentException = ex;
				lastException = ex;
			}
		}
		if (result != null) {
			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}
			// If the parent AuthenticationManager was attempted and successful then it
			// will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent
			// AuthenticationManager already published it
			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}

			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).
		if (lastException == null) {
			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
					new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
		}
		// If the parent AuthenticationManager was attempted and failed then it will
		// publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
		// parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}
		throw lastException;
	}

코드 설명

사전에 변수를 지정해준다.
앞에서 설명했듯, 로직상 일단 Exception이나 null 값은 일단 보류하고 하나씩 시도를 해본다.
그러다가 null 이 아닌 값이 나오면 그 값을 반환하는 로직이기 때문에 아래와 같은 변수를 만든다.
부모와 관련된 변수들은 조금 이따 나오겠지만, 만약 알맞은 provider을 찾지 못했을 때 부모 AuthenticationProvider 가 있으면 한번 훑을 때 필요하다.

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		//넘겨받은 인자의 클래스
		Class<? extends Authentication> toTest = authentication.getClass();
        //마지막 예외, 부모 예외 keep 하기 위해
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
        //결과, 부모의 결과 keep 하기 위해
		Authentication result = null;
		Authentication parentResult = null;
        //나중에 로그 남길 때 씀
		int currentPosition = 0;
		int size = this.providers.size();

아래는 갖고 있는 AuthenticationProvider을 하나씩 반복하며 진행한다.
InternalAuthenticationServiceException
AccountStatusException

for (AuthenticationProvider provider : getProviders()) {
			//supports() 메서드는 이 provider가 해당 타입을 지원하는지 boolean 값으로 알려준다.
			if (!provider.supports(toTest)) {
				continue; //지원하지 않으면 바로 다음 provider로
			}
            //로그
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
            	//지원하므로 AuthenticationProvider의 authenticate 메서드를 호출한다.
				result = provider.authenticate(authentication); //Authentication result
				if (result != null) { // null 이 아니면 (인증되면)
                	//복사해준다 : 주어진 Authentication detail -> 만들어진 Authentication detail
					copyDetails(authentication, result); 
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
            	//AccountStatusException : 계정이 잠기거나 비활성화 같이 계정의 상태에 따라 발생
                //InternalAuthenticationServiceException : 내부적으로 발생한 시스템 문제로 인해 인증 요청을 처리하지 못한 경우 발생, 위 링크 참조
				prepareException(ex, authentication);
                // -> this.eventPublisher.publishAuthenticationFailure(ex, auth);
                
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw ex;
			}
			catch (AuthenticationException ex) {
            	//AuthenticationException 발생하면 일단 변수에 저장하고 보류
				lastException = ex;
			}
		}

parent가 있으면 parent도 동일한 과정을 시도
만약 null 이 아닌 값이 찾아졌으면, credential 지우기

		if (result == null && this.parent != null) {
			// Allow the parent to try. 부모가 있으면 시도
			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)) {
				// Authentication 이 완료됬으므로 Authentication에서 credentials를 삭제
				((CredentialsContainer) result).eraseCredentials();
			}
			// parent 에서 시도 하고 성공했으면 이미 AuthenticationSuccess()가 발생했으므로
            // 중복으로 발생시키는 것을 방지한다.
			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}
            // result가 있으면 리턴해준다.
            // 여기서 만들어진 Authentication result는 똑같은 Authentication 오브젝트 이지만,
            // 역할이 다르다.
			return result;
		}
		// Parent was null, or didn't authenticate (or throw an exception).
		if (lastException == null) {
			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
					new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
		}
		// If the parent AuthenticationManager was attempted and failed then it will
		// publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
		// parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}
		throw lastException;
	}

3. AuthenticationProvider

AuthenticationProvider은 인터페이스다. 이를 구현해서 각 Authentication 에 대한 적절한 처리를 한다.

public interface AuthenticationProvider {
	// Authentication 에 대해 인증을 진행한다.
    // ProviderManager(AuthenticationManager) 에서 루프를 돌면서 호출함
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
    
	// 이 Authentication 이 처리 가능한지 알려주는 메서드
    // 이 또한 ProviderManager 에서 호출한다.
	boolean supports(Class<?> authentication);

}

4. DaoAuthenticationProvider

여기서 예를 들 것은 가장 기본적인 username/password 인증을 하는 DaoAuthenticationProvider로 예시를 들 것이다.
AbstractUserDetailsAuthenticationProvider를 extend 하는데 이는 추상메서드이다.

.supports()

AbstractUserDetailsAuthenticationProvider에서 구현되어서 상속된다.
이 authentication 타입을 지원하는지 체크한다

	@Override
	public boolean supports(Class<?> authentication) {
		return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
	}

.authentication(Authentication authentication)

AbstractUserDetailsAuthenticationProvider에서 구현되어서 상속된다.
인증을 진행하는 핵심 메서드이다.

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    	//타입 체크
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));
		// 대충 설명하면 determineUsername() -> authentication.getName으로 가져오기
		String username = determineUsername(authentication);
        // 캐시를 사용했으면, 캐시 관련 동작을 한다.
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
			cacheWasUsed = false;
			try {
            	//핵심 메서드, User정보를 가져오는 부분
            	//retrieveUser는 직접 UserDetails을 추출하는 메서드로
                // 이곳에서 UserDetailsService를 호출해서 UserDetails를 만든다
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException ex) {
				this.logger.debug("Failed to find user '" + username + "'");
				if (!this.hideUserNotFoundExceptions) {
					throw ex;
				}
				throw new BadCredentialsException(this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
			}
			Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
		}
		try {
        	//핵심 메서드, 받은 UserDetail을 체크한다.
        	//잠겼는지,비활성화 됬는지, 만료됬는지 체크
			this.preAuthenticationChecks.check(user);
            //추상메서드 이므로 DaoAuthenticationProvider 에서 구현해주어야함
            //인증이 끝난 후 추가적으로 체크해야할 것들이 들어감
            //DaoAuthenticationProvider에서는 비밀번호가 있는지, 그리고 비밀번호가 맞는지 체크함
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException ex) {
			if (!cacheWasUsed) {
				throw ex;
			}
			// 캐시에서 사용된 데이터를 업데이트 한다.
			cacheWasUsed = false;
			user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
            // 그리고 다시 체크
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		this.postAuthenticationChecks.check(user);
		if (!cacheWasUsed) { //캐시에 저장
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
        //this.forcePrincipalAsString 은 맨 마지막에 리턴되는 Principal 값을 String으로 내보낼지 정의한다.
        //보통 일반적인 경우라면 UserDetail을 사용하여 구별하며 UserDetail은 추가적인 정보를 제공하므로 유용하다.
        //https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.html#isForcePrincipalAsString()        
		if (this.forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
        //리턴하는 Authentication 만들어서 리턴한다.
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}
  • retrieveUser()
  • additionalAuthenticationChecks()
  • createSuccessAuthentication()

핵심 메서드 를 아래서 한번 살펴보자.
이 메서드들은 모두 DaoAuthenticationProvider에 구현되어있다.

retrieveUser()

UserDetailsService로 부터 UserDetails를 생성하는 역할을 맡고 있다.
DaoAuthenticationProvider에 구현되어있다.

	@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()

DaoAuthenticationProvider에 구현되어있다.
일반적으로 Authentication.getCredentials()UserDetails.getPassword() 를 비교하는 로직이 들어간다.

	@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"));
		}
	}

createSuccessAuthentication()

DaoAuthenticationProvider에 구현되어있다.
Authentication을 만들어서 리턴해준다.

	@Override
	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
			UserDetails user) {
		boolean upgradeEncoding = this.userDetailsPasswordService != null
				&& this.passwordEncoder.upgradeEncoding(user.getPassword());
		if (upgradeEncoding) {
			String presentedPassword = authentication.getCredentials().toString();
			String newPassword = this.passwordEncoder.encode(presentedPassword);
			user = this.userDetailsPasswordService.updatePassword(user, newPassword);
		}
		return super.createSuccessAuthentication(principal, authentication, user);
	}

upgradeEncoding이 의미하는 것은 이제 이중으로 비밀번호를 인코딩할 것인지를 의미한다.
우리가 BCryptEncoder? 같은거로 해싱을 하는데 이를 두번 돌려서 암호화 하는 옵션도 존재한다.
이는 공식 문서와 api에 자세히 나와있다.
아래는 super.createSuccessAuthentication(principal, authentication, user);

	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
			UserDetails user) {
		// Ensure we return the original credentials the user supplied,
		// so subsequent attempts are successful even with encoded passwords.
		// Also ensure we return the original getDetails(), so that future
		// authentication events after cache expiry contain the details
		UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal,
				authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());
		this.logger.debug("Authenticated user");
		return result;
	}

여기서는 맨 처음과 똑같이 UsernamePasswordAuthenticationToken을 만들어서 리턴하는데 잘 봐야할 곳은 UsernamePasswordAuthenticationToken.authenticated()메서드 이다.
이는 static method로 UsernamePasswordAuthenticationToken.authenticated(), .unauthenticated 두가지 static 메서드가 정의 되어있다.
이는 생성자와 비슷하게 인스턴스를 생성해서 리턴해주고, isAuthenticated가 true 이냐 false냐 이다.
이 의미는 처음 authenticationManager.authenticate(Authenticate authenticate)에 넣어주는, 즉 유저가 입력한 정보를 전달하는 역할의 Authentication은 unauthenticated 로
이렇게 인증이 완료된 유저를 createSuccessAuthentication을 통해서 리턴해줄때는 authenticated 된 유저를 리턴해주게 된다.

5.Authentication 처리

authenticationManager.authencation()을 통해 인증을 시도하고, 위의 일련의 과정을 거친 후 인증이 성공하면 Authentication 객체를 받게 된다. 이를 ContextHolder에 등록하던, 어떻게 하던 처리를 하면 된다.
나의 경우 이 글의 맨 처음 시작 코드로 돌아가면 JwtFilter가 받아서 적절한 처리를 하게 된다.

쓰는데 너무 오래걸렸다. 2일 넘게...
사실 전에 SpringSecurity api와 doc을 영어로 읽고 있었는데, 되게 완벽하게 이해되기 힘들었다.
하지만 이렇게 코드를 하나씩 따라 올라가면서 동작 코드를 분석해보니, 정말 직관적으로 이해되기 시작했다.
이 코드 역시 하나의 소통수단이자 언어라는 생각이 들었고, 이 SpringSecurity 코드를 작성한 사람들의 코드가 마치 하나의 언어처럼 쭉 읽으며 이해가 된다는 것이 참 배우고 싶다는 생각이 많이 들었다.
몇몇 개념은 검색과 공식문서를 봐야했지만, 사실 대부분의 흐름과 관련된 내용은 코드를 '읽으면서' 이해할 수 있었다.
앞으로도 계속해서 SpringSecurity를 학습하고 미래의 나를 위해 글을 남기 예정이다.

profile
더 좋은 구조를 고민하는 개발자 입니다

2개의 댓글

comment-user-thumbnail
2023년 12월 20일

AuthenticationManager의 implements가 5개 정도 있는걸로 아는디 어떻게 providerManager가 구현체로 동작하신다는 걸 아셨나요?

1개의 답글