[TIL] Spring Security 2 (인증처리)
코드스테이츠 백엔드 부트캠프 D+94
주말이라는 것은… 사람을 참 나태하게 만들기도
활력을 공급하기도 하는 것 같다.
토요일까지 Spring Security를 공부하다가 번아웃이와
일요일에 너무 게으르게 누워만 있었더니
유튜브 나태지옥에 다시 빠져버렸다.
저번주에 Spring Security를 사용하는 예제 코드와
간단한 흐름을 알아보았다.
그중 Filter라는 것들을 배웠고 Spring Security도
Filter로 구성된 FilterChain으로 구동하는 것도 배웠다.
오늘은 그 중에서 인증관련된 Filter를 자세히 알아보자
Spring Security 인증
Filter 흐름 복습
저번주에 공부했었던 (Spring Security 기초)의 흐름을 다시한번 짚고 넘어가자
클라이언트에서 요청이오면
Filter들의 모임인 FilterChain을 거쳐 하나씩 실행한다.
그 중 FilterChainProxy 클래스로 부터 보안을 위한 작업 필터모음인
SecurityFilterChain를 수행하게된다.
여기서 SecurityFilterChain도 Filter들로 이루어져있고
아래의 사진이 SecurityFilterChain의 대략적인 구조이다.
사진에서 보면 알 수 있듯이
SecurityFilterChain의 구조는
SecurityContextPersistenceFilter
LogoutFilter
UsernamePasswordAuthenticationFilter
...
등 여러가지 필터를가진 형태로 존재한다.
모든 Filter의 구조를 알고 이해한다면 분명 SpringSecurity에 대한
이해도가 깊어질 것이자만, 현재 공부를 시작한지 얼마안된
내가 전부를 이해하기에는 사실 무리가 있는 것 같다고 생각이 들었다.
이 중에서도 인증과 관련된 UsernamePasswordAuthenticationFilter의
인증 처리 흐름을 이해해보려한다. 해당하는 부분을 어제 코드로 구현하기도 했고
흐름을 알아야 SpringSecurity를 기본적인 내용을 구현하는데 무리가 없을 것 같다.
Security Filter 확인 방법?
위에서 얘기했듯이 여러 Filter들을 거쳐서
인증과 권한에 대한 수행을 SpringSecurity가 진행해준다.
실제로 어떠한 Filter를 거쳐왔는지 확인을 할 수 있는 애노테이션이 있고
디버그 용도로 매우 유용하게 쓰일 것 같다.
@Configuration
@EnableWebSecurity(debug = true) // 디버그 설정
public class SecurityConfigurationV2 {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin()
.loginPage("/auths/login-form")
.loginProcessingUrl("/process_login")
.failureUrl("/auths/login-form?error")
.and()
.authorizeHttpRequests()
.anyRequest()
.permitAll();
return http.build();
}
}
@EnableWebSecurity(debug = true) 어노테이션을
이용해 현재 요청에 이용된 Filter의 목록이 조회가 가능하다.
애플리케이션을 실행시키고
formLogin 방식으로 요청을 보내보았다.
그리고 콘솔을 확인해보면
Security filter chain: [
DisableEncodeUrlFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
UsernamePasswordAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
AuthorizationFilter
]
첫번쨰로 요청에 대한 정보들과
다음으로는 Security filter chain에 Filter 목록들이
나오는 것을 확인할 수 있다.
위에서 볼 수 있듯이 우리가 알아보려고하는
UsernamePasswordAuthenticationFilter 클래스도
콘솔로 확인이 가능하다.
인증 처리
이제 본격적으로 인증이 어떠한 과정으로
진행되는지 처리 순서에 대한 흐름을 알아보자.
(1). 클라이언트에서 로그인 요청이 오면, SecurityFilterChain
을 거치게된다.
그 중 인증에 관한 부분은 UsernamePasswordAuthenticationFilter
클래스이다.
(2). Authentication
객체를 생성한다.
여기서 로그인할때 받아온 Username과 Password를 이용해
Authentication
의 구현체인 UsernamePasswordAuthenticationToken
클래스를 이용해 Authentication
객체를 만든다.
(❗️Authentication 객체는 인증이 완료되지 않음)
(3). Authentication
객체를 AuthenticationManager
로 전달한다.
여기서 구현체인 ProviderManager
가 인증이라는 작업의 총괄 매니저다.
총괄은 하지만 실제 인증처리는 AuthenticationProvider
에게 위임한다.
(❗️Authentication 객체는 인증이 완료되지 않음)
(4). Authentication
객체를 AuthenticationProvider
로 전달한다.
ProviderManager
가 전달해준 Authentication
객체로 인증하는 작업을 진행한다.
인증을 구현하는 방법은 2가지 정도가 있다.
AuthenticationProvider
를 상속받아 구현UserDetailsService
를 상속받아 구현
(Spring Security 기초)에서 우리는 UserDetailsService
를
상속받아 직접 클래스를 구현했었다.
여기서 1번방법으로 구현하면 ₩
UserDetailsService` 부분은
사용하지 않고 구현을 할 수 있다. 구현할때 중요한 부분은
직접 Credentials과 Password를 인증과정 로직을 구현해야한다는 점이다.
하지만 어제 우리가 구현했던 2번방법으로 구현하면
AuthenticationProvider
를 구현하지 않아도된다.
DaoAuthenticationProvider
를 구현 클래스로 사용하기 때문이다.
실제로 호출 흐름을 본다면,
ProviderManager
클래스의.authenticate();
메서드 호출AuthenticationProvider
인터페이스의AbstractUserDetailsAuthenticationProvider
가
.authenticate();
메서드를 구현했음..authenticate();
메서드안에서 인증 처리를 위한retrieveUser();
호출
DaoAuthenticationProvider
클래스가.retrieveUser();
를 구현했음.retrieveUser();
메서드에서UserDetailsService
클래스의.loadUserByUsername()
메서드 호출.loadUserByUsername();
메서드는 우리가 구현해야하는 부분이다.
(❗️Authentication 객체는 인증이 완료되지 않음)
(5). 즉, 위에서 얘기했던 DaoAuthenticationProvider
클래스가
UserDetailsService
를 이용해 UserDetails
를 객체를 조회 한다.
실제로 어제 예제에서 UserDetails
를 상속받아
username, password, 권한정보를 객체로 만들어 구현하였고
해당객체를 UserDetailsService
를 이용해 반환하는 프로그램도 작성했다.
(❗️Authentication 객체는 인증이 완료되지 않음)
(6). 즉, Credential 저장소라는 뜻은 DB의 암호화된 비밀번호를 가져온다는 뜻이다.
쉽게 말하자면 (5)번을 통해 호출된 UserDetailsService
에서
loadUserByUsername(String username);
메서드를 이용해
우리는 어떤 username으로 접근하는지 알 수 있으며, 해당 정보로 DB를 조회해
싫제로 동일한 username이 존재할 경우 해당 테이블의 정보를 가져올 수 있다.
즉, 권한정보, 암호화된 비밀번호 등등 사용자 정보를 가져올 수 있다는 뜻이다.
(7). 이제 우리는 (6)번을 통해서 조회한 데이터로 인증을 비교할
UserDetails
객체를 만들 수 있다. UserDetails
객체는
username,password,권한정보를 가지고 있으면 객체로 만들 수 있다.
(8). 만들어진 UserDetails
객체는, 맨처음 호출했던
DaoAuthenticationProvider
클래스에 반환하게 되어진다.
아까 얘기했듯이 AuthenticationProvider
를 구현한 클래스로
인증을 처리해주는 클래스이다.
(9). UserDetails
정보를 이용해 DaoAuthenticationProvider
클래스가
.mitigateAgainstTimingAttack();
메서드를 호출해 인증을 처리한다.
인증처리가 정상적으로 이루어졌을 경우에
그림과 같이 인증정보인 Collection<GrantedAuthority>
가 추가된
Authentication
객체를 반환하게 된다.
(⭕️Authentication 객체는 인증이 완료됨)
(10). 이제 인증이 완료된 객체를 호출했던
ProviderManager
로 반한된다.
(⭕️Authentication 객체는 인증이 완료됨)
(11). 마찬가지로 처음에 호출을 했던
UsernamePasswordAuthenticationFilter
클래스로
인증이완료된 객체가 반환되게 되어진다.
(⭕️Authentication 객체는 인증이 완료됨)
(12). 최종적으로 인증이 완료된 Authentication 객체를
SecurityContextHolder
를 이용해 SecurityContext
에 저장을하게 된다.
SecurityContext
의 세션 정책에 따라서
HttpSession에 저장되어 사용자의 인증 상태를 유지하기도 하고
HttpSession을 생성하지 않고 무상태를 유지하기도 한다.
이렇게 오늘은 SecurityFilterChain에서
어떻게 인증이 이루어지는지 알아보았다.
오늘 정말 파급적인 효과로 코드에 대해 이해를 했다.
아키텍쳐를 그려가면서, 실제 코드들이 호출되서
실행되는 것을 하나하나 따라가보면서 순서에 대한 흐름을 글로 정리해봤다.
이렇게 코드를 따라가면서, 파악한 내용을 적고 하다보니까
어디서 부터 시작되었고, 어떻게 객체를 돌려받는지가
파악되다보니까 어느정도 인증처리에 대한 흐름은 잡힌 것 같다.
오늘 공부는 여기서 끝 !!
오늘의 커피량: ☕️ ☕️
오늘의 점심: 된장찌개, 라면, 밥