[Spring Security] AbstractAuthenticationProcessingFilter 격파하기!

k·2024년 2월 23일
0

spring security

목록 보기
3/3

오늘은 AbstractAuthenticationProcessingFilter에 대해서 격파한 이후 내가 이해한 내용을 정리하려한다.

AbstractAuthenticationProcessingFilter

🧐그게 뭔데..?

UsernamePasswordAuthenticationFilter 이라고 있다. 이게 뭔지 궁금할 것 같은데 아래 사진을 보면 이전에 설명에서 나왔던 사진인 것을 알 수 있다.

해당 SecurityFilterChain에 3번째에 존재하는 필터이다.

AbstractAuthenticationProcessingFilter에 대해서 알려준다고 했는데 다른 필터가 나와서 아마 의아할 것이다.

하지만 해당 UsernamePasswordAuthenticationFilter의 내부를 보면 왜 해당 Filter가 등장했는지 알 수 있을 것이다.

위와 같이 UsernamePasswordAuthenticationFilter는 AbstractAuthenticationProcessingFilter를 상속받고 있는 것을 알 수 있다 .

그럼 UsernamePasswordAuthenticationFilter는 뭐하는 필터일까?

UsernamePasswordAuthenticationFilter는 Spring Security에서 기본적으로 지원하는 필터이며, Username과 Password를 form-data 형식으로 받아서 인증로직을 처리하는 필터이다.

위 코드는UsernamePasswordAuthenticationFilter내에서 오버라이드된 함수이다.

함수명으로 추측하면 인증을 시도하는 메소드라는 것을 알 수있다. 해당 함수에서는 위에서 말한 것처럼 username과 pasword를 obtain~()함수를 통해서 받아온다.

request.getParameter(form-data Key)를 통해 form-data형태의 데이터를 return 해주는 것을 알 수 있다. 해당 데이터를 UsernamePasswordAuthenticationToken으로 감싸서 AuthenticationManger내의 authenticate에 해당 Token을 넘겨주며 인증을 진행하게 된다.

여기까지가 UsernamePasswordAuthenticaitonFilter의 로직에 대한 간단한 설명이다.

그럼 본론으로 넘어와서 AbstractAuthenticationProcessingFilter가 뭐야?

이름에서부터 볼 수 있듯이 추상클래스이다. 공통적인 작업은 해당 추상클래스 내부에서 동작될 수 있다. 예를 들어서 아래와 같이 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가지 요소를 살펴보면 된다.

  1. formLogin(AbstractHttpConfigurer::disable)
    해당 코드를 통해서 formLogin을 비활성화했고, 이에 따라 UsernamePasswordAuthenticationFilter가 실행되지 않게 된다.

  2. 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함수의 첫번째 인스턴스로 호출해주면 된다.

profile
You must do the things you think you cannot do

0개의 댓글