AuthenticationProvider and Housekeeping

mainimg.jpg

AuthenticationProvider

고객의 요구사항은 다양할 수 있다. 예를 들어, 어떤 고객은 애플리케이션에서 인증을 수행하는데 유저이름과 비밀번호를 사용하고 싶을 수 있고, 어떤 고객은 OAuth2를 사용하고 싶을 수도 있다. 또 어떤 고객은 몇몇 서비스에 OTP를 적용하는 것을 고려할지도 모른다. 이렇게 수 많은 경우의 요구사항을 프레임워크에서 모두 준비해 줄 수 없기 때문에, 우리는 우리만의 AuthenticationProvider를 커스텀하여 사용해야 한다.

AuthenticationProvider

public interface AuthenticationProvider {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;

	boolean supports(Class<?> authentication);

}

AuthenticationProvider내에는 두 가지 추상 메서드가 있다.
첫 번째는 Authenticate메서드이다. 반환하는 Authentication객체는 인증이 성공적이었는지에 대한 정보를 가지고 있어야 AuthenticationManager가 다른 AuthenticationProvider를 시도해 볼 수 있다.
두 번째는 supports메서드이다. 해당 메서드는 우리가 커스텀하는 AuthenticationProvider가 어떤 종류의 인증을 지원하고 싶은지를 서술한다. 어떤 종류의 인증에 관해 이 AuthenticationProvider가 부름을 받아야 하는지 프레임워크에 알려주는 역할이다. 예시는 다음과 같다.

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

HouseKeeping

AuthenticationProvider를 호출한 것은 필연적으로 AuthenticationManager일 텐데, AuthenticationProvider에서 반환한 Authentication객체에 비밀번호와 같은 민감한 값이 들어있다면, 이것을 제거해줘야 한다. AuthenticationManager가 이 값을 제거하는 역할을 하며, 구현체 중 하나인 ProviderManager는 다음과 같이 동작한다.

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    //... 생략
    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);
            throw ex;
        }
        catch (AuthenticationException ex) {
            lastException = ex;
        }
    }
    //...생략...
    if (result != null) {
        if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
            ((CredentialsContainer) result).eraseCredentials();
        }
        if (parentResult == null) {
            this.eventPublisher.publishAuthenticationSuccess(result);
        }

        return result;
    }
    //...생략
}
  1. for문을 통해 각 AuthenticationProvider가 현재 Authentication 구현체를 지원하는지 확인한다.
  2. result에 인증 정보가 반환된다.
  3. result != null인 경우 Authentication객체의 credentials 정보를 파기한다.
  4. 이로써 result에는 인증완료(ture)여부만 남고, 비밀번호 값은 null이 된다.