시큐리티 인증 / 인가 흐름도
- Servlet Filter
- DelegationFilterProxy
- 최초 요청을 에서 받아 스프링 컨테이너에 있는 FilterChainProxy에게 요청 전달
- FilterChainProxy
- 넘겨받은 요청을 여러 필터에 전달
- 각 필터는 해당 요청을 처리하고 AuthenticationFilter로 연결
- DelegationFilterProxy
- Authentication
- AuthenticationFilter
- 인증 처리 수행
- 인증 성공 시 Authentication 객체를 생성하여 AuthenticationManager에게 전달
- AurthenticationManager
- AuthenticationProvider에게 인증 위임
- AuthenticationProvider는 사용자 인증 정보를 확인
- UserDetailsService를 통해 사용자 정보 조회
- UserDetailsService는 최종적으로 UserDetails 객체 반환
- AuthenticationProvider
- PasswordEncoder를 사용하여 비밀번호가 일치하는 지 확인
- 비밀번호 일치 시 Authentication 객체 생성
→ 인증 객체 ⇒ 유저 객체와 권한 정보를 저장 - AuthenticationManager에게 Authentication 객체 반환
- SecurityContextHolder
- 최종적으로 AuthenticationFilter는 SecurityContextHolder에 Authentication 객체를 저장
- AuthenticationFilter
Authentication
- 특정 자원에 접근하려는 사람의 신원을 확인하는 방법을 의미
→ 너는 누구냐? - 사용자 인증의 일반적인 방법은 사용자 이름과 비밀번호를 입력하게 하는 것으로서 인증이 수행되면
신원을 알고(인증) 권한 부여(인가) 가능 - 사용자의 인증 정보를 저장하는 토큰 개념의 객체로 활용되며 인증 이후 SecurityContext 에 저장되어 전역적으로 참조 가능
구조
- getPrincipal() : 인증 주체를 의미하며 인증 요청의 경우 사용자 이름을, 인증 후 에는 UserDetails 타입의 객체가 될 수 있음
- getCredentials() : 인증 주체가 올바른 것을 증명하는 자격 증명으로서 대개 비밀번호를 의미
- getAuthorities() : 인증 주체(principal)에게 부여된 권한을 나타냄
- getDetails() : 인증 요청에 대한 추가적인 세부 사항을 저장한다. IP 주소, 인증서 일련 번호 등
- isAuthenticated() : 인증 상태 반환
- setAuthenticated(boolean) : 인증 상태를 설정
인증 절차 흐름
- 클라이언트가 id,pw를 입력하여 로그인 시도
- AuthenticationFilter가 사용자가 입력한 정보로 Authentication 객체 생성하여 AuthenticationManager에게 전달
- 인증 처리 성공 시 Authentication 객체를 다시 생성하여 아래의 정보를 저장하여 반환
- principal ⇒ 시스템에서 가지고 온 사용자 정보가 저장(유저 정보)
- credentials ⇒ 비밀번호는 노출되면 안되므로 null 상태
- authorities ⇒ 사용자에게 부여된 권한(GrantedAuthority 타입의 컬렉션 제공)
- AuthenticationFilter는 최종 인증 결과를 받아 SecurityContextHolder의 SecurityContext에 저장
코드 흐름 정리
사용자 username, passowrd 입력
- UsernamePasswordAuthenticationFilter의 attemptAuthentication() 메서드 실행
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
username = username != null ? username.trim() : "";
String password = this.obtainPassword(request);
password = password != null ? password : "";
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
- 사용자가 입력한 username, password를 이용하여 UsernamePasswordAuthenticationToekn 객체 생성
public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, Object credentials) {
return new UsernamePasswordAuthenticationToken(principal, credentials);
}
- UsernamePasswordAuthenticationToken 생성자
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null); // 권한 X
this.principal = principal; // username
this.credentials = credentials; // password
this.setAuthenticated(false); // 인증 false
}
- this(UsernamePasswordAuthenticationFilter).getAuthenticationManager().authenticate(authRequest)
→ AuthenticationManager의 authenticate() 메서드
→ ProviderManager의 authenticate() 메서드
→ ProviderManager ⇒ AuthenticationManager 인터페이스의 구현체- provider.authenticate() 메서드에게 위임
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...
while(var9.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var9.next();
...
try {
result = provider.authenticate(authentication);
...
- DaoAuthenticationProvider.retrieveUser() 메서드
- AbstractUserDetailsAuthenticationProvider.authenticate() 메서드에서 이동
- this.getUserDetailsService().loadUserByUsername(username)의 UserDetailsService는 빈으로 선언해둔 것
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
- SecurityConfig에서 선언한 Bean
@Bean
public UserDetailsService userDetailsService(){
UserDetails user = User.withUsername("user").password("{noop}1111").roles("USER").build();
return new InMemoryUserDetailsManager(user);
}
- 여기까지가 username을 가지고 사용자 정보를 가지고옴 ⇒ 이제 password와 검증
- DaoAuthenticationProvider의 additionalAuthenticationChecks() 메서드
- 비밀번호가 일치한 지 확인
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
...
} else {
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"));
}
}
}
- AbstractUserDetailsAuthenticationProvider의 createSuccessAuthentication() 메서드
- 유저 정보, 비밀번호, 권한 정보를 담아 생성한 인증 객체 반환
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
this.logger.debug("Authenticated user");
return result;
}
- 인증 성공한 인증 객체를 반환 받아 AbstractAuthenticationProcessingFilter의 successfulAuthentication() 메서드에서 SecurityContext에 저장
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authResult);
this.securityContextHolderStrategy.setContext(context);
this.securityContextRepository.saveContext(context, request, response);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
'Language > Spring Security' 카테고리의 다른 글
인증 관리자 - AuthenticationManager (0) | 2024.07.24 |
---|---|
인증 컨텍스트 - SecurityContext / SecurityContextHolder (0) | 2024.07.16 |
요청 캐시 - RequestCache / SavedRequest (0) | 2024.07.16 |
로그 아웃 - logout() (0) | 2024.07.16 |
익명 사용자 - anonymous() (0) | 2024.07.16 |