SecurityFilterChain 빈을 스프링 컨테이너에 등록함으로써 저희는 서블릿에 필터를 적용할 수 있음을 저번 포스팅에서 다뤘습니다.
또한, 여러 SecurityFilterChain 을 구성함과 동시에 요청에 부합하는 단 하나의 SecurityFilterChain을 실행시킬 수 있음을 알았습니다.
FilterChainProxy에 등록되는 SecurityFilterChain은 이를 결정하기 위해 SecurityMatcher를 사용하며, FilterChainProxy는 이를 바탕으로 요청에 따라 실행될 SecurityFilterChain을 선택합니다.
FilterChainProxy는 이전 포스팅에서 언급했듯이 여러 SecurityFilterChain을 관리하는 스프링 컨테이너의 빈입니다. 서블릿 컨테이너의 필터에 존재하는 DelegatingFilterProxy가 FilterChainProxy 빈을 등록했음에 따라, 우리는 여러 SecurityFilterChain을 FilterChainProxy에 등록하고, 개발자의 의도에 맞게 적절한 FilterChain을 실행할 수 있습니다.
(자세한 정보는 Spring Security Architecutre에 대해 다룬 이전 포스팅을 확인해주시기 바랍니다 )
SecurityMatcher를 SecurityFilterChain에 등록함에 따라 해당 SecurityFilterChain이 언제 실행될지 FilterChainProxy가 정할 수 있습니다. SpringSecurity는 아래와 같은 방법으로 SecurityFilterChain에 매칭 정보를 등록할 수 있도록 합니다.
조건을 결정하는 두가지 방법이 있습니다. 아래의 첫번째 사진처럼 SecurityMatcher를 단순히 url정보만을 통해 결정하도록 하는 방법과 아래의 두번째 사진과 같이 RequestMatcher를 통해 결정하는 방법입니다.
이번 포스팅에서는 RequestMatcher 를 통해 SecurityFilterChain에 이 SecurityMatcher를 적용하여, 각각의 요청에 적합한 SecurityFilterChain이 실행되는지 테스트하기 위한 코드를 구현해보겠습니다.
먼저, 두가지 SecurityFilterChain을 구성합니다. securityMatcher 메소드를 이용하며, 해당 메소드의 파라미터로 전달된 RequestMatcher 인스턴스로 SecurityFilterChain이 실행될지 말지를 결정하도록 합니다.
Spring Security는 기본적으로 적용하도록 하는 몇가지 필터가 있습니다. AuthorizationFilter 또한 그중 하나이며, 커스텀한 필터를 필터사이에 넣기 위해서는 반드시 순서를 정의해야 합니다. 따라서 위의 코드처럼 커스텀한 필터가 AuthorizationFilter 이전에 실행되도록 구현했습니다.
다음은 각각의 SecurityFilterChain에 적용되는 두 필터입니다. 실행될때 서로 다른 메세지가 콘솔창에 출력되도록 했습니다.
자, 이제 RequestMatcher를 구현해 보겠습니다. 구성한 RequestMatcher는 요청 메세지의 헤더의 아래와 같은 필드를 확인하여, 적절한 SecurityFilterChain이 실행되도록 구현했습니다.
실행 테스트
요청에 대한 응답을 확인하기 위해 HTTPie를 사용했습니다.
http -v GET localhost:8080 filter:1
http -v GET localhost:8080 filter:2
순서대로 실행했을 때, 아래와 같은 결과값이 나오는것을 확인할 수 있었습니다.
전체 코드 구성은 다음과 같습니다.
@Configuration
@EnableWebSecurity
public class SecurityConfigure {
private static final String HEADER = "filter";
private static final String FILTER_1 = "1";
private static final String FILTER_2 = "2";
@Bean
public SecurityFilterChain securityFilterChain1(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.securityMatcher(new RequestMatcher1())
.addFilterBefore(new Filter1(), AuthorizationFilter.class)
.build();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.securityMatcher(new RequestMatcher2())
.addFilterBefore(new Filter2(), AuthorizationFilter.class)
.build();
}
private static class Filter1 extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
System.out.println(new Date(System.currentTimeMillis()) + " : " + "SecurityFilterChain1 executed!!!!!!");
filterChain.doFilter(request, response);
}
}
private static class Filter2 extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
System.out.println(new Date(System.currentTimeMillis()) + " : " + "SecurityFilterChain2 executed!!!!!!");
filterChain.doFilter(request, response);
}
}
private static class RequestMatcher1 implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
if (request.getHeader(HEADER).equals(FILTER_1)) {
return true;
}
return false;
}
}
private static class RequestMatcher2 implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
if (request.getHeader(HEADER).equals(FILTER_2)) {
return true;
}
return false;
}
}
}
이상으로 포스팅을 마치겠습니다. 감사합니다.
출처 : https://docs.spring.io/spring-security/reference/servlet/architecture.html
'SpringBoot' 카테고리의 다른 글
[SpringBoot] 스프링의 에러처리 탐구 (0) | 2024.01.17 |
---|---|
[Spring] Spring Security 인증 구성 (0) | 2024.01.02 |
[Spring] Spring Security Architecture (1) | 2023.12.29 |
[Spring] RestTemplate (1) | 2023.12.29 |
SpringBoot의 작동원리를 직접 구현 해보며 이해하자(1) - Servlet (0) | 2023.12.22 |