인증 처리 흐름과 인증 컴포넌트 구조도

인증 처리 흐름 ( Spring Security Filter Chain ) 및 각 인증 컴포넌트
AbstractAuthenticationProcessingFilter :
1) 필터역할을 하기위한 doFilter() 메서드를 가짐
2) 필터링의 시작 : doFilter()를 UsernamePasswordAuthenticaitonFilter에서 사용하게 해서 시작
3) 필터링의 끝 : 인증 성공된 Authetication을 Security Context에 저장
AbstractAuthenticationProcessingFilter
UsernamePasswordAuthenticaitonFilter :
1) AbstractAuthenticationProcessingFilter 클래스를 extend받아 필터역할됨
2) 로그인 폼을 통해 전송되는 request parameter 디폴트값 : username, password
3) AntPathrequestMatcher : 클라이언트의 URL에 매치되는 매처. 파라미터는 ( "/login", HTTP_Method )
: AbstractAuthenticationProcessingFilter의 메서드에 @Overrride해서 사용
3) 인증받을 Token 생성점 (UsernamePasswordAuthenticationToken) <= 인증 성공한 토큰과는 무관
4) AuthenticationManager에 인증 작업 위임
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { // (1)
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; // (2)
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; // (3)
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login","POST");
UsernamePasswordAutheticationToken :
username(principal) / password(credential) / GrantedAuthority(인가 권한)을 포함한
AbstractAutjemticationToken을 상속받은 UsernamePasswordAuthenticationToken을 포함한
Authentication을 Security Context에 저장
즉, (username / password / GrantedAuthority) < Token < Authentication < Security Context
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
private Object credentials;
...
// 인증받아야할 토큰
public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, Object credentials) {
return new UsernamePasswordAuthenticationToken(principal, credentials);
}
// 인증 받은 토큰
public static UsernamePasswordAuthenticationToken authenticated(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
return new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
}
...
Authentication :
인증 자체를 표현하는 인터페이스
Security Context에서 리턴받거나, 저장되는 형태
1) Principal : 사용자 식별자. username/password 기반 인증에선 username을, 다른 인증방식에선 UserDetails을 말함
2) Credential : 패스워드. 인증이 일어난 직후, ProviderManager가 해당 Credentials를 삭제함
3) Authorities : 사용자 접근 권한 목록(AuthenticationProvider가 부여). 일반적으로 SimpleGrantedAuthority클래스
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
AuthenticationManager :
autheticate() 메서드 하나만 정의되있음. 실질적 인증 관리는 구현클래스인 ProviderManager가 함
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
ProviderManager :
1) AuthenticationProvider 관리 및 인증 처리 위임 역할
2) 코드 흐름
: Bean등록을 통해 List<AuthenticationProvider> 객체를 DI 받음
=> DI 받은 List를 이용해 for문으로 적절한 AuthenticationProvider 찾음
=> 적절한 AuthenticationProvider를 찾으면, 인증 처리 위임
=> 인증이 완료되면 Credentials 제거
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
...
// ProviderManager 클래스가 Bean으로 등록 시, List<AuthenticationProvider> 객체를 DI 받음
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();
// DI 받은 List를 이용해 for문으로 적절한 AuthenticationProvider를 찾음
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 {
// 적절한 AuthenticationProvider를 찾았다면 해당 AuthenticationProvider에게 인증 처리를 위임
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)) {
// 인증이 정상적으로 처리되었다면 인증에 사용된 Credentials를 제거
((CredentialsContainer) result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
...
}
...
}
AuthenticationProvider :
1) 실질적인 인증 수행 담당 컴포넌트
2) DaoAuthenticationProvider : Username/Password 기반 인증 처리 담당.
UserDetailsService로부터 전달받은 UserDetails을 이용해 인증처리함
AuthenticationProvider인터페이스를 구현한 AbstractUserDetailsAuthenticationProvider클래스를 상속한 확장 클래스
따라서, 실질적인 인증처리는 AbstractUserDetailsAuthenticationProvider 추상클래스의 authenticate() 메서드
(DAO : Data Access Object)
3) DaoAuthenticationProvider와 AbstractUserDetailsAuthenticationProvider의 호출순서
- AbstractUserDetailsAuthenticationProvider의 authenticated() 메서드 호출
- DaoAuthenticationProvider의 retrieveUser() 메서드 호출
- DaoAuthenticationProvider의 additionalAuthenticationChecks() 메서드 호출
- DaoAuthenticationProvider 의 createSuccessAuthentication() 메서드 호출
- AbstractUserDetailsAuthenticationProvider 의 createSuccessAuthentication() 메서드 호출
- 인증된 Authentication을 ProviderManager에게 리턴
// AutheticatiomProvider 인터페이스의 구현 클래스가 AbstractUserDetailsAuthenticationProvider이고, Dao~~는 AbstractUserDetailsAuthenticationProvider를 상속한 확장 클래스
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
...
private PasswordEncoder passwordEncoder;
...
// USerDetailsService로부터 UserDetails를 조회하는 역할 겸, 인증된 Authentication 객체 생성 역할
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); // (2-1)
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);
}
}
// PasswordEncoder를 이용한 패스워드 검증
@Override
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();
// 클라로부터 전달받은 패스워드와 DB에서 조회한 패스워드가 일치하는지 검증
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"));
}
}
...
UserDetails : Username / Password / Authority 등이 담긴 컴포넌트
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities(); // (1) 권한 정보
String getPassword(); // (2) 패스워드
String getUsername(); // (3) Username
boolean isAccountNonExpired(); // (4)
boolean isAccountNonLocked(); // (5)
boolean isCredentialsNonExpired(); // (6)
boolean isEnabled(); // (7)
}
UserDetailsService : UserDetails를 로드하는 인터페이스
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
SecurityContext / SecurityContextHolder :
1) SecurityContext : 인증된 Authetication 객체를 저장하는 컴포넌트 (getter와 setter가있다)
2) SecurityContextHolder : SecurityContext 관리 역할
코드상, SecurityContextHolder는 클래스 SecurityContext는 객체 타입
public class SecurityContextHolder {
...
private static SecurityContextHolderStrategy strategy;
...
public static SecurityContext getContext() {
return strategy.getContext();
}
...
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
...
}
'Codestates [Back-end] > 데일리 로그 [TIL]' 카테고리의 다른 글
| 22.09.23 SpringSecurity - 인가 (0) | 2022.09.23 |
|---|---|
| 22.09.23 SpringSecurity - Filter / FilterChain 구현 (0) | 2022.09.23 |
| 22.09.21 SpringSecurity - 개요 (0) | 2022.09.21 |
| 22.09.20 인증/보안 - 기초 (0) | 2022.09.20 |
| 22.09.19 Section3 기술면접 준비 (1) | 2022.09.19 |