Spring Security - 1. DelegatingFilterProxy 알아보기

INCHEOL'S·2021년 2월 26일
12

spring-security

목록 보기
1/5
post-custom-banner

Spring Security DelegatingFilterProxy 알아보기

안녕하세요. INHCEOL'S 입니다.

지난번 사내 프로젝트에서 Spring Security를 제가 모두 직접 적용해 보면서 스프링 레퍼런스 페이지와 여러 참고 자료 그리고 강의 등을 통해 학습해 나갔는데요.

제가 학습해왔던 Spring Security의 전반적인 내용과 공식 documentation 페이지를 참고하여

여러분들께 조금이나마 공유하고 저도 정리 차원에서 글을 남기고자 합니다^^.

모든 내용을 한번에 적기에는 내용이 방대할 것 같아 Spring Security에서 핵심적인 클래스들

하나 하나씩 예제 코드를 통해 어떻게 확장 및 변경이 가능한지 알아보도록 하겠습니다.

오늘 소개드릴 내용은 Spring SecurityDelegatingFilterProxy 입니다.

환경
SpringBoot 2.4.1
Java 1.8

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

소개
1. DelegatingFilterProxy
2. SecurityFilterAutoConfiguration
3. DelegatingFilterProxyRegistrationBean

1. DelegatingFilterProxy

Spring Security는 사용하고자 하는 FilterChain들을 Servlet Container 기반의 필터 위에서 동작시키기 위해 DelegatingFilterProxy라는 클래스를 이용합니다.
DelegatingFilterProxy는 IOC 컨테이너에서 관리하는 빈이 아닌 표준 서블릿 필터를 구현하고 있으며 내부에 위임대상(FilterChainProxy)을 갖고있습니다.
이 녀석은 표준 서블릿 컨테이너와 Spring IOC 컨테이너의 다리 역할을 한다고 생각하시면 됩니다.
위 그림은 Spring Security 공식 레퍼런스 페이지에서 가져왔는데요, 주의 하실 점은 위 그림의 FilterChain은 Servlet Container가 관리하는 ApplicationFilterChain 입니다. 우리가 나중에 살펴볼 SecurityFilterChain과는 다른 녀석이라는 점을 알아주세요.

즉, DelegatingFilterProxy는 서블릿 필터이며, Spring IOC 컨테이가 관리는 Filter Bean을 갖고 있고 이 Filter Bean은 FilterChainProxy이며 이 객체안에서 Security와 관련된 일들이 벌어진다고 생각할 수 있겠습니다.
(위 그림에서 DelegatingFilterProxy안에 Bean Filter는 FilterChainProxy가 됩니다.)

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 void invokeDelegate(
			Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		delegate.doFilter(request, response, filterChain);
	}

위 코드는 DelegatingFilterProxy에서 선언된 필드와 doFilter 메서드 부분을 가져온 것입니다.
코드를 보면 이 필터의 책임은 deletgate 라는 Filter에 다시 doFilter로 파라미터들을 전달하고 있습니다.

그럼 이 DelegatingFilterProxy는 어떻게 생성되는지 알아보겠습니다.
저는 Spring Boot 환경을 기준으로 말씀드리겠습니다.

2. SecurityFilterAutoConfiguration

@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 Boot 환경을 사용하게 되면 자동 설정에 의해 DelegatingFilterProxy를 간편하게 설정할 수 있습니다. (사실 우리가 할 건 없습니다. 단지 spring-boot-starter-security 의존성만 추가하면 될 뿐...)

여기서 @ConditionalOnBean(name = DEFAULT_FILTER_NAME)을 보면
DelegatingFilterProxyRegistrationBean 빈을 생성하는 조건이 DEFAULT_FILTER_NAME을 가진 빈이 있어야 생성하는데 AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME을 타고 들어가보시면
"springSecurityFilterChain"라는 이름이고 이 녀석은 FilterChainProxy 빈의 이름입니다.

즉, FilterChainProxy 빈이 있어야지만 DelegatingFilterProxy를 생성할 수 있도록 해놨고
DEFAULT_FILTER_NAME(springSecurityFilterChain)을 DelegatingFilterProxyRegistrationBean 생성자로 전달하며 이 DelegatingFilterProxy안에서는 이 빈네임을 가지고 ApplicationContext에서 빈을 꺼내와 delegate를 셋팅합니다.

	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;
	}

디버깅을 통해 delegate에 셋팅된 객체를 확인해보면 FilterchainProxy임을 알 수 있습니다.

그럼 마지막으로 Servlet Container Filter에 이 필터를 등록할 수 있게 해주는 DelegatingFilterProxyRegistrationBean에 대해서 알아보겠습니다.

3. DelegatingFilterProxyRegistrationBean

자동 설정 코드에서는 DelegatingFilterProxyRegistrationBean 빈 객체를 선언하고 있습니다. 이 빈을 통해서 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;
	}

}

DelegatingFilterProxyRegistrationBean 코드를 보시면
AbstractFilterRegistrationBean 클래스를 상속하며 @Override한
getFilter 메서드에서 새로운 DelegatingFilterProxy를 생성하고 있는 것을 보실 수 있습니다.

여기서 상속한 AbstractFilterRegistrationBean에서 상속구조를 따라가다 보면 ServletContextInitializer을 구현하고 있는데 이는 서블릿 컨테이너에 DelegatingFilterProxyRegistrationBean에서 getFilter가 만들어낸 필터를 등록하게 됩니다.
ServletContextInitializer은 Servlet 3.0+ 이상에서만 지원되니 참고하시구요. ServletContainer 기동시 Java Config를 이용한 방법에서는 ServletContextInitializer을 활용하니 이에 대해 궁금하신분은 찾아보셔도 좋을 것 같습니다.

오늘의 결론.

Spring Security를 사용한다면, DelegatingFilterProxy가 생성된다. 이는 Spring Boot 사용시 SecurityFilterAutoConfiguration을 통해서 DelegatingFilterProxyRegistrationBean 빈을 생성하며 이 빈은 DelegatingFilterProxy 필터를 생산해내는 빈으로서 ServletContextInitializer을 통해 서블릿 컨테이너의 필터체인에 DelegatingFilterProxy을 등록하도록 지원한다.

끝.~!

profile
제주하르방백년초콜릿 먹고싶네요. 아, 저는 백엔드 개발자입니다.
post-custom-banner

1개의 댓글

comment-user-thumbnail
2022년 9월 28일

Autoconfig 까지 자세한 설명 감사합니다~ 도움이 되었습니다

답글 달기