오늘은 AbstractAuthenticationProcessingFilter에 대해서 격파한 이후 내가 이해한 내용을 정리하려한다.
UsernamePasswordAuthenticationFilter 이라고 있다. 이게 뭔지 궁금할 것 같은데 아래 사진을 보면 이전에 설명에서 나왔던 사진인 것을 알 수 있다.
해당 SecurityFilterChain에 3번째에 존재하는 필터이다.
AbstractAuthenticationProcessingFilter에 대해서 알려준다고 했는데 다른 필터가 나와서 아마 의아할 것이다.
하지만 해당 UsernamePasswordAuthenticationFilter의 내부를 보면 왜 해당 Filter가 등장했는지 알 수 있을 것이다.
위와 같이 UsernamePasswordAuthenticationFilter는 AbstractAuthenticationProcessingFilter를 상속받고 있는 것을 알 수 있다 .
UsernamePasswordAuthenticationFilter는 Spring Security에서 기본적으로 지원하는 필터이며, Username과 Password를 form-data 형식으로 받아서 인증로직을 처리하는 필터이다.
위 코드는UsernamePasswordAuthenticationFilter내에서 오버라이드된 함수이다.
함수명으로 추측하면 인증을 시도하는 메소드라는 것을 알 수있다. 해당 함수에서는 위에서 말한 것처럼 username과 pasword를 obtain~()함수를 통해서 받아온다.
request.getParameter(form-data Key)를 통해 form-data형태의 데이터를 return 해주는 것을 알 수 있다. 해당 데이터를 UsernamePasswordAuthenticationToken으로 감싸서 AuthenticationManger내의 authenticate에 해당 Token을 넘겨주며 인증을 진행하게 된다.
여기까지가 UsernamePasswordAuthenticaitonFilter의 로직에 대한 간단한 설명이다.
이름에서부터 볼 수 있듯이 추상클래스이다. 공통적인 작업은 해당 추상클래스 내부에서 동작될 수 있다. 예를 들어서 아래와 같이 AuthenticationManager를 get/set하는 작업이 있다. 그리고 추상 메소드로 인증을 시도하는 것에 대한 것을 주고 커스텀할 수 있는 기회를 제공한다.
AuthenticationManager란?
이전에 그림으로 한번 설명한 적이 있는데 간단하게 다시 설명해보겠다.
이 처럼 인터페이스이고 ProviderManger가 해당 인터페이스의 구현체가 된다.
ProviderManger에서는 여러 인증에 대한 것을 대응할 수 있도록 AuthenticationProvider를 생성자로 가져간다.주로 DaoAuthenticationProvider를 사용해서 데이터베이스 내에 Username과 Password가 존재하는지에 대한 인증 작업을 진행하게 된다.
주로 spring security는 form-data 기반의 로그인을 기본으로 지원하고 있다. 하지만 개발자가 api 형태에서 context/json
형태로 데이터를 받아서 로그인을 처리하고 싶을 수도 있다. 그럴 때, AbstractAuthenticationProcessingFilter를 상속받아 커스텀마이징해서 만들 수있다.
이런 질문이 아마 머릿 속에서 떠오를 수도 있다.
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.formLogin(formLogin -> formLogin
.loginPage("/login"));
return http.build();
}
}
해당 코드는 Spring Security 공식 문서에 있던 코드이다. 해당 Bean을 등록함으로써, filterChain동작을 제어할 수 있다.
위에 요소들은 이후 소개할 예정이고, 지금 중요하게 봐야할 포인트는 .formLogin(~) 쪽이다.
람다식 형태로 지원되고 있고, 기본적인 loginPage를 /login
이라는 것을 알려주고 있다. 해당 조건이 실행 되게 되면 Spring Security에서 기본적으로 지원하는 UsernamePasswordAuthenticationFilter가 진행되게된다.
그런데, 우리는 커스텀한 Filter를 사용하고 싶다. 그렇기 때문에 위의 코드를 아래와 같이 변경해준다.
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.formLogin(AbstractHttpConfigurer::disable)
.addFilterAfter(커스텀필터의 인스턴스, LogoutFilter.class);
return http.build();
}
}
간단하게 2가지 요소를 살펴보면 된다.
formLogin(AbstractHttpConfigurer::disable)
해당 코드를 통해서 formLogin을 비활성화했고, 이에 따라 UsernamePasswordAuthenticationFilter가 실행되지 않게 된다.
addFilterAfter(커스텀필터의 인스턴스, LogoutFilter.class);
해당 코드를 통해서 LogoutFilter 이후에 커스텀 필터를 배치하게 된다.
근데 왜 LogoutFilter 이후에 배치하는 걸까?
위에서 가져온 사진을 또 가져왔다. 해당 왼쪽에 있는 SeucrityFilterChain을 보면 LogoutFilter 이후에 UsernamePasswordAuthenticationFilter가 존재한다.우리는 formLogin을 disable해주면서 해당 filter를 비활성화 했고 그 자리에 커스텀필터를 배치하기 위해서 해당 코드를 작성한 것이다.
위의 작업을 진행했으면 추가적으로 @Bean으로 등록해야하는 요소들이 정말 많다.
@Bean //등록
public AuthenticationManager authenticationManager() throws Exception {
DaoAuthenticationProvider provider = @Bean으로 등록된 Provider
return new ProviderManager(provider);
}
//json 형태로 username, password 를 받아서 인증 진행 예시
@Bean //등록
public JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordLoginFilter() throws Exception {
JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordLoginFilter = new JsonUsernamePasswordAuthenticationFilter(objectMapper);
jsonUsernamePasswordLoginFilter.setAuthenticationManager(authenticationManager());
return jsonUsernamePasswordLoginFilter;
}
위와 같이 셋팅했다면 jsonUsernamePasswordLoginFilter()를 .addFilterAfter
함수의 첫번째 인스턴스로 호출해주면 된다.