SpringSecurity [AuthenticationManager]
Authentication 을 만들고 인증을 처리하는 인터페이스 AuthenticationManager에 대해서 알아 보겠다.
잠시 확인을 하자면
ContextHolder 는 Authentication 의 정보를 담고있는 것이고.
AuthenticationManager는 실제 인증을 담당한다.
인증 하는 동안 무슨일이 일어나는지 알아보겠다.
AuthenticationManager는 authenticate 라는 메서드 하나를 갖는다. 매우 조촐한데
Authentication 객체를 파라미터를 받고있으며 이 객체는 정보를 담고 있다. (user가 보낸 username, password등 )
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
인터페이스이기 때문에 우리는 구현체를 보통 가져다가 쓴다. 바로
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
ProviderManager 이다. 물론 AuthenticationManager 를 implements받아서 사용해도 되지만 보통 이렇게 사용한다.
ProviderManager 클래스 안에는
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
이렇게 @Override 되어있는데 여기를 한번 디버거를 통해 어떤 정보들이 오고가는지 확인해보고자 한다.

디버거를 통해 로그인을 실행했을 때
authentication 이라는 이름으로 내가 입력한 정보들이 담겨있는 것을 볼 수 있다.
username = user / cksdntjd (여기서 유저인데 중간에 cksdntjd으로 바뀜... 혼자 테스트하다가 꼬였음 ㅠㅠ)
password = 123
해당 authentication 은

UsernamePasswordAuthenticationToken 이다. 하지만
@Override된 메서드를 쭉 보다보면

AuthenticationProvider 는 UsernamePasswordAuthenticationToken 을 가지고있지 않고 AnonymousAuthenticationProvider를 가지고있다. 즉 처리를 못한다는 것이다.
이후 다음 if문에서 걸리게 되고

해당 if문을 try 문을 통해서 parent 로 들어가게 된다.

이번에는
이 provider는 새로운 provider다 햇갈리지 말자
AnonymousAuthenticationProvider 가 아닌 DaoAuthenticationProvider가 등장했다.

아까는 여기서 걸리게 되었지만 이번에는 어떻게 되는지 확인해 보자

이렇게 내부에서 UsernamePasswordAuthenticationToken 을 처리할 수 있다. .
계속 저 support 메소드가 신경 쓰이는데
AnonymousAuthenticationProvider.class 를 받는 support 메서드와
UsernamePasswordAuthenticationToken.class 를 받는 support 메서드로 구분되어있다 .
부모 클래스가 UsernamePasswordAuthenticationToken클래스를 받는다.

이렇게 결국 result 에 authenticate 메서드를 이용해서 넣을 수가 있다.
여기서 쓰이는 authenticate 메서드는
AbstractUserDetailsAuthenticationProvider.class
에 존재한다.

계속해서 정보를 옮기는 중인건데
그래서 이게 언제 인증을 해주는것 인지 확인을 해야한다.
저 retrieveUser 메서드가 포인트다 드디어 우리의 코드와 연결되는 구간이다.

내가 적은 유저의 아이디가 노출이 된다. 또 들어가보자
DaoAuthenticationProvider.class
로 들어오게 되는데

여기서 저 getUserDetailsService().loadUserByname(username); 이 매우매우 익숙하다.
@Service
public class AccountService implements UserDetailsService {
여기에 선언해놓은

유저의 정보들이다. 결국 여기서 User의 정보 (security의 유저임)
import org.springframework.security.core.userdetails.User;
가 return 되는 것을 알수 있다.
결국 최종적으로
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; //여기여기
}
우리는 result 를 반환하는데
result 는 첫번째 try문에서 선언이 되고 [ result = provider.authenticate(authentication); ]

결국 이 result 는 Authentication 객체이다.
이 Authentication 은 인증 과정을 계속 해서 거치다가. 완료 되면
SecurityContextHolder
로 들어오게 되고
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
해당 Authentication 객체로 꺼내 유저의 정보를 우리가 코드로 꺼내 사용 가능한 것이다.