본문 바로가기

기술 블로그

Spring Security에서의 Filter

안녕하세요.

이번 글에서는 Spring Security가 어떻게 Filter로 등록되고 동작하는지에 대해서 정리하고자 합니다.

'필터와 인터셉터란?' 포스팅의 연장선에 있는 글이므로 아래를 참고하시면 이해하는데 도움이 될 수 있습니다.

https://okkkk-aanng.tistory.com/35

 

필터와 인터셉터란?

안녕하세요.이번 글에서는 필터와 인터셉터에 대한 내용을 다루고자 합니다.'필터와 인터셉터를 내가 과연 제대로 알고 있을까?'와 같은 반성을 하게 된 개인적인 사건(?)이 있었고, 이에 대한

okkkk-aanng.tistory.com

 

DelegatingFilterProxy와 SecurityFilterChain

DelegatingFilterProxy를 통해 서블릿 컨테이너는 스프링 컨텍스트에 Bean으로 등록된 Filter를 호출할 수 있습니다. 스프링 시큐리티도 DelegatingFilterProxy를 활용하여 구현되어 있습니다. 웹 어플리케이션이 실행될 때, 아래와 같이 DelegatingFilterProxy를 등록합니다.

public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer {
    private void insertSpringSecurityFilterChain(ServletContext servletContext) {
        String filterName = "springSecurityFilterChain";
        DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
        String contextAttribute = this.getWebApplicationContextAttribute();
        if (contextAttribute != null) {
            springSecurityFilterChain.setContextAttribute(contextAttribute);
        }

        this.registerFilter(servletContext, true, filterName, springSecurityFilterChain);
    }
}

 

DelegatingFilterProxy를 통해 springSecurityFilterChain이라고 등록된 Bean에게 보안 관련 처리를 위임하고 있었습니다. 

 

그렇다면 springSecurityFilterChain을 이름으로 갖는 Bean은 어디서 등록되는지를 확인했고, 이는 WebSecurityConfiguration 설정 클래스에 존재하는 것을 확인했습니다.

@Configuration(
    proxyBeanMethods = false
)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {

    private WebSecurity webSecurity;
    private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();
    
    @Bean(
        name = {"springSecurityFilterChain"}
    )
    public Filter springSecurityFilterChain() throws Exception {
    	// ...
       Iterator var7 = this.securityFilterChains.iterator();

        while(true) {
            while(var7.hasNext()) {
                SecurityFilterChain securityFilterChain = (SecurityFilterChain)var7.next();
                this.webSecurity.addSecurityFilterChainBuilder(() -> {
                    return securityFilterChain;
                });
                Iterator var5 = securityFilterChain.getFilters().iterator();

                while(var5.hasNext()) {
                    Filter filter = (Filter)var5.next();
                    if (filter instanceof FilterSecurityInterceptor) {
                        this.webSecurity.securityInterceptor((FilterSecurityInterceptor)filter);
                        break;
                    }
                }
            }

            var7 = this.webSecurityCustomizers.iterator();

            while(var7.hasNext()) {
                WebSecurityCustomizer customizer = (WebSecurityCustomizer)var7.next();
                customizer.customize(this.webSecurity);
            }

            return (Filter)this.webSecurity.build();
        }
    
    }
}

 

 WebSecurityConfiguration 설정 클래스는 springSecurityFilterChain을 빈으로 등록하고 있으므로, 스프링 시큐리티를 사용하는 어플리케이션은 위 설정 클래스를 @Import 하여 커스텀 환경설정 구성이 필요합니다. 이를 위해 사용되는 것이 @EnableWebSecurity 어노테이션 입니다. 예를 들면 아래와 같습니다.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
    boolean debug() default false;
}
@EnableWebSecurity
public class SecurityConfig {
 
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    	// ...
        return http.build();
    }
}

 

즉, 어플리케이션에 맞는 커스텀 스프링 시큐리티 설정 클래스는 @EnableWebSecurity 어노테이션을 통해 springSecurityFilterChain을 Bean으로 등록할 수 있었고, 서블릿 컨테이너를 DelegatingFilterProxy를 통해 보안 관련 필터로 위 체인을 호출할 수 있는 구조였습니다.

 

 

Spring Security 6

 Spring Boot 3.0 이상의 버전에서 지원하는 Spring Security 6 버전 부터는 DelegatingFilterProxy를 사용하지 않고, FilterProxyChain을 직접 서블릿 필터에 등록하여 보안 필터를 구성합니다. 참고한 소스는 아래와 같습니다. 

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration {

    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain(List<SecurityFilterChain> securityFilterChains) {
        return new FilterChainProxy(securityFilterChains); // SecurityFilterChain들을 FilterChainProxy로 래핑
    }
}
@Component
public class SecurityInitializer implements ServletContextInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        Filter springSecurityFilterChain = ...; // FilterChainProxy
        servletContext.addFilter("springSecurityFilterChain", springSecurityFilterChain)
                      .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
    }
}

 

서블릿 컨테이너에서 스프링 컨텍스트에 저장된 Bean을 필터로 사용하기 위해서는 DelegatingFilterProxy가 필요했습니다. 왜냐하면 서블릿 컨테이너에서 직접 스프링 컨텍스트의 Bean에 접근할 수 없었기 때문입니다. 그러나, 스프링 부트의 최신 버전부터는 서블릿에서도 스프링 컨텍스트를 관리할 수 있게 되었고, 이에 따라 DelegatingFilterProxy는 굳이 사용하지 않고 서블릿 컨테이너에 필터를 직접 등록하여 사용할 수 있게 되었습니다. 이러한 설계 사상에 맞추어 Spring Security 6에서는 DelegatingFilterProxy를 제거하였다는 것을 파악할 수 있었습니다.

 

 

감사합니다.

'기술 블로그' 카테고리의 다른 글

@Transactional Deep Dive  (1) 2025.02.19
Java Annotation  (2) 2025.02.18
Java ObjectMapper (Feat. RedisTemplate)  (2) 2025.02.13
스프링 Kafka Consumer Deep Dive - 1  (1) 2025.02.05
필터와 인터셉터란?  (0) 2024.12.11