DelegatingFilterProxy
SecurityFilterChain은 Spring Security에서 관리하는 필터 체인으로서 Spring IOC Container에서 빈으로 정의된다. 이는 Servlet Container가 관리하는 ApplicationFilterChain과는 별개로 동작한다.여기서 DelegatingFilterProxy는 Spring Security의 SecurityFilterChain을 Servlet Container 기반의 필터 위에서 동작시키기 위한 클래스이다.
DelegatingFilterProxy는 Servlet Filter로 동작하고, 내부적으로 Spring IOC Container에서 빈으로 관리되는 FilterChainProxy을 가지고 있다. 위의 그림에서 Bean Filter에 해당된다.
DelegatingFilterProxy는 AppplicationContext에서 springSecurityFilterChain란 이름으로 생성된 빈을 찾아 요청을 위임한다. (= FilterChainProxy)
이후 FilterChainProxy 객체 내에서 Security와 관련된 작업이 이루어진다고 볼 수 있다.
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {
private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
if (securityProperties.getFilter().getDispatcherTypes() == null) {
return null;
}
return securityProperties.getFilter().getDispatcherTypes().stream()
.map((type) -> DispatcherType.valueOf(type.name()))
.collect(Collectors.toCollection(() -> EnumSet.noneOf(DispatcherType.class)));
}
}
- Spring ApplicationContext 에 DEFAULT_FILTER_NAME 이란 이름을 가진 빈이 존재할 때만 DelegatingFilterProxyRegistrationBean 을 생성한다.
public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer { public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain"; }
- 여기서 AbstractSecurityWebApplicationInitializer 을 타고 들어가보면 DEFAULT_FILTER_NAME 에 해당하는 이름이 springSecurityFilterChain 임을 확인할 수 있다.
- 이는 앞서 살펴본 FilterChainProxy 의 빈 이름에 해당한다.
- 이를 DelegatingFilterProxyRegistrationBean 생성자의 매개변수로 전달하며, 이후 DelegatingFilterProxy 를 생성할 때 요청을 위임할 대상의 빈 이름으로 지정한다.
- 즉, FilterChainProxy 빈이 있어야지만 DelegatingFilterProxy 를 생성할 수 있다.
public class DelegatingFilterProxyRegistrationBean extends AbstractFilterRegistrationBean<DelegatingFilterProxy>
implements ApplicationContextAware {
private ApplicationContext applicationContext;
private final String targetBeanName;
/**
* Create a new {@link DelegatingFilterProxyRegistrationBean} instance to be
* registered with the specified {@link ServletRegistrationBean}s.
* @param targetBeanName name of the target filter bean to look up in the Spring
* application context (must not be {@code null}).
* @param servletRegistrationBeans associate {@link ServletRegistrationBean}s
*/
public DelegatingFilterProxyRegistrationBean(String targetBeanName,
ServletRegistrationBean<?>... servletRegistrationBeans) {
super(servletRegistrationBeans);
Assert.hasLength(targetBeanName, "TargetBeanName must not be null or empty");
this.targetBeanName = targetBeanName;
setName(targetBeanName);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
protected String getTargetBeanName() {
return this.targetBeanName;
}
@Override
public DelegatingFilterProxy getFilter() {
return new DelegatingFilterProxy(this.targetBeanName, getWebApplicationContext()) {
@Override
protected void initFilterBean() throws ServletException {
// Don't initialize filter bean on init()
}
};
}
private WebApplicationContext getWebApplicationContext() {
Assert.notNull(this.applicationContext, "ApplicationContext be injected");
Assert.isInstanceOf(WebApplicationContext.class, this.applicationContext);
return (WebApplicationContext) this.applicationContext;
}
}
- getFilter 메서드에서 DelegatingFilterProxy를 생성하고 있는 것을 볼 수 있다. 여기서 첫 번째 매개변수로 전달되는 targetBeanName은 DelegatingFilterProxyRegistrationBean 생성 시 설정된 springSecurityFilterChain에 해당된다.
- 해당 클래스는 AbstractFilterRegistrationBean을 상속하고 있으며, 상속구조를 따라가면 ServletContextInitializer를 통해 Servlet Container의 FilterChain에 DelegatingFilterProxy를 등록하는 역할을 수행함을 알 수 있다.
public class DelegatingFilterProxy extends GenericFilterBean {
@Nullable
private String contextAttribute;
@Nullable
private WebApplicationContext webApplicationContext;
@Nullable
private String targetBeanName;
private boolean targetFilterLifecycle = false;
@Nullable
private volatile Filter delegate;
private final Object delegateMonitor = new Object();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
}
- initDelegate 메서드는 delegate 필터를 초기화하는 역할을 수행하는데,
앞서 DelegatingFilterProxy를 생성하면서 설정된 targetBeanName인 springSecurityFilterChain을 가지고 Spring의 ApplicationContext에서 FilterChainProxy를 가져와서 delegate 필터로 위임한다.- 즉, delegateToUse는 FilterChainProxy가 되는 것이고 invokeDelegate 메서드의 매개변수로 전달되어 doFilter 메서드를 통해 SecurityFilterChain이 수행된다.
정리
DelegatingFilterProxy는 Spring Security의 보안 필터 체인을 Servlet Container의 필터 위에서 동작시키기 위한 클래스로, Spring IOC Container에서 빈으로 관리되는 FilterChainProxy를 사용하여 요청을 위임한다.
이를 가능하게 하는데는 SecurityFilterAutoConfiguration이 해당 빈을 등록하고, DelegatingFilterProxyRegistrationBean이 DelegatingFilterProxy를 생성하여 Servlet Container의 필터 체인에 등록한다. 이후 이를 위임한 FilterChainProxy는 실제 보안 작업을 처리한다.