ProviderManager.java
package sprout.security.authentication;
import sprout.beans.InfrastructureBean;
import sprout.beans.annotation.Component;
import sprout.security.authentication.exception.*;
import sprout.security.core.Authentication;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
public class ProviderManager implements AuthenticationManager, InfrastructureBean {
private final AuthenticationEventPublisher eventPublisher;
private final List<AuthenticationProvider> providers;
private final AuthenticationManager parent;
public ProviderManager(AuthenticationEventPublisher eventPublisher, List<AuthenticationProvider> providers, AuthenticationManager parent) {
// null 방지 및 불변 리스트로 설정
this.eventPublisher = Objects.requireNonNullElseGet(eventPublisher, NullEventPublisher::new);
this.providers = Optional.ofNullable(providers).orElse(Collections.emptyList());
this.parent = parent;
}
public ProviderManager(List<AuthenticationProvider> providers) {
this(new NullEventPublisher(), providers, null);
}
@Override
public Authentication authenticate(Authentication authentication) throws LoginException {
Class<? extends Authentication> toTest = authentication.getClass();
LoginException lastException = null;
for (AuthenticationProvider provider : this.providers) {
if (!provider.supports(toTest)) {
continue;
}
try {
Authentication result = provider.authenticate(authentication);
if (result != null) {
eventPublisher.publishAuthenticationSuccess(result);
return result; // 성공적으로 인증되었으므로 결과 반환
}
} catch (LoginException ex) {
if (ex instanceof AccountExpiredException || ex instanceof CredentialExpiredException) {
eventPublisher.publishAuthenticationFailure(ex, authentication);
throw ex; // 계정 상태 관련 예외는 즉시 던짐 (스프링과 유사)
}
// 그 외 인증 실패 예외는 저장해두고 다음 Provider 시도
lastException = ex;
System.out.println("Authentication failed with provider " + provider.getClass().getSimpleName() + ": " + ex.getMessage());
} catch (Exception ex) {
lastException = new AuthenticationException("Internal authentication service error: " + ex.getMessage());
eventPublisher.publishAuthenticationFailure(lastException, authentication);
throw lastException;
}
}
// 현재 Provider들로 인증에 실패했고, 부모 AuthenticationManager가 있다면 위임
if (lastException != null && this.parent != null) {
try {
Authentication parentResult = this.parent.authenticate(authentication);
if (parentResult != null) {
eventPublisher.publishAuthenticationSuccess(parentResult);
return parentResult; // 부모가 성공하면 결과 반환
}
} catch (LoginException ex) {
lastException = ex;
}
} else if (this.parent != null) { // 현재 Provider가 없거나 지원하는 Provider가 없는 경우
// 모든 Provider가 지원하지 않았고, 부모가 있다면 부모에게 위임
try {
Authentication parentResult = this.parent.authenticate(authentication);
if (parentResult != null) {
eventPublisher.publishAuthenticationSuccess(parentResult);
return parentResult; // 부모가 성공하면 결과 반환
}
} catch (LoginException ex) {
lastException = ex;
}
}
// 모든 시도가 실패했을 때 최종 예외 던지기
if (lastException == null) {
throw new ProviderNotFoundException("No AuthenticationProvider found for " + toTest.getName());
} else {
eventPublisher.publishAuthenticationFailure(lastException, authentication);
throw lastException;
}
}
// 테스트나 디버깅을 위한 getter
public List<AuthenticationProvider> getProviders() {
return providers;
}
private static final class NullEventPublisher implements AuthenticationEventPublisher {
@Override
public void publishAuthenticationFailure(LoginException exception, Authentication authentication) {
}
@Override
public void publishAuthenticationSuccess(Authentication authentication) {
}
}
}