티스토리 뷰

728x90

https://mossgreen.github.io/Servlet-Containers-and-Spring-Framework/

브라우저에서 사용자의 요청이 있을 때 서블릿 컨테이너부터 스프링 컨테이너까지 처음부터 끝까지 요청과 응답에는 Servlet Filter를 거치게 된다. 서블릿 컨테이너에서는 FilterChain을 통해서 등록된 필터를 계속 거쳐가는 것이다. 

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// do something before the rest of the application
    chain.doFilter(request, response); // invoke the rest of the application
    // do something after the rest of the application
}

스프링 시큐리티는 서블릿 컨테이너 필터 기반으로 보안 처리를 하고 스프링의 기술을 사용한다. 이렇듯 스프링 시큐리티에서 사용하는 여러 필터들을 서블릿 컨테이너에서 동작하게 하기 위해서는 스프링 빈을 서블릿 필터에 등록해야 하는데 서블릿 필터는 스프링에서 정의된 빈을 주입해서 사용할 수가 없다. 그래서 스프링 시큐리티에서 사용하고자 하는 FilterChain들을 서블릿 컨테이너의 필터 위에 등록시키기 위해서 DelegatingFilterProxy 클래스를 이용한다.

이름에서 알수 있듯이 Delegating 한다는 것은 위임을 한다는 클래스이다. 요청을 받아서 스프링에서 관리하는 필터에 위임을 하는 역할을 하는 것이다. DelegatingFilterProxy 클래스가 직접적으로 보안적인 처리를 하는 클래스는 아니고 단순히 스프링 컨테이너에 위임을 하는 역할만 한다. 즉 DelegatingFilterProxy 클래스는 서블릿 컨테이너가 관리하는 서블릿 필터 클래스이고 스프링 컨테이너는 요청을 위임받아서 스프링 시큐리티의 FilterChain이 여러 필터들을 거치면서 인증/인가 작업을 한다.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// Lazily get Filter that was registered as a Spring Bean
	// For the example in DelegatingFilterProxy
    // delegate is an instance of Bean Filter0
	Filter delegate = getFilterBean(someBeanName);
	// delegate work to the Spring Bean
	delegate.doFilter(request, response);
}

DelegatingFilterProxy는 특정한 이름을 가진 스프링 빈을 찾아서 그 빈에게 요청을 위임하는데 springSecurityFilterChain이라는 이름으로 생성된 빈을 ApplicationContext에서 찾아서 요청을 위임한다.

위의 이미지가 springSecurityFilterChain이라는 이름으로 생성되는 필터 빈이다.

전체적인 흐름을 서블릿 스펙을 구현하는 서블릿 컨테이너와 스프링 빈들을 관리하는 스프링 컨테이너 영역으로 나눈 이미지로 봐보면 아래와 같다.

  • 최초 사용자 요청이 서블릿 컨테이너 Filter에서 처음으로 받는다. 요청에 대해서 필터들이 처리하게 된다.
  • 그중에 DelegatingFilterProxy 필터 클래스를 요청을 받게 되면 이 클래스는 전달받은 요청을  특정 이름을 가진 빈(springSecurityFilterChain)을 찾아서 Spring Container로 요청을 위임한다.
  • 스프링에서는 springSecurityFilterChain으로 등록된 빈이 FilterChainProxy인데 이 빈 안에에 있는 여러 보안 필터를 실행한다.
  • 이후 보안이 다 통과되면 서블릿 컨테이너의 필터에 대한 요청이 다 마무리되면 스프링 컨테이너에 디스패처 서블릿으로 요청이 들어가게 되는 것이다.

SecurityFilterAutoConfiguration.java

DelegatingFilterProxy를 등록하는 과정이 아래에 있다. 빈을 등록할 때 DEFAULT_FILTER_NAME이 springSecurityFilterChain이다.

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

}

DelegatingFilterProxy.java

아까도 얘기하였듯이 실제로 보안 처리를 하지 않고 요청을 위임하는 역할만 하는 클래스이다.

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

}

이후에 FilterChainProxy 클래스에서 보안 필터 처리를 시작하는 것이다.

public class FilterChainProxy extends GenericFilterBean {

	...

	private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
		HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
		List<Filter> filters = getFilters(firewallRequest);
		if (filters == null || filters.size() == 0) {
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
			}
			firewallRequest.reset();
			chain.doFilter(firewallRequest, firewallResponse);
			return;
		}
		if (logger.isDebugEnabled()) {
			logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
		}
		VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
		virtualFilterChain.doFilter(firewallRequest, firewallResponse);
	}

	
	private List<Filter> getFilters(HttpServletRequest request) {
		int count = 0;
		for (SecurityFilterChain chain : this.filterChains) {
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,
						this.filterChains.size()));
			}
			if (chain.matches(request)) {
				return chain.getFilters();
			}
		}
		return null;
	}

    ...

}

마무리

마무리를 해보자면  서블릿 필터에 스프링 빈을 주입해서 사용할 수 없으므로 DelegatingFitlerProxy 라는 서블릿 필터가 스프링 컨테이너에 요청을 위임한다.

처음 스프링부트 프로젝트를 실행하면 SecurityFilterAutoConfiguration에서 DelegatingFilterProxy 필터를 등록을 한다. 필터 이름을 springSecurityFitlerChain으로 등록한다. 그리고 요청을 스프링 컨테이너에 위임하고 DelegatingFilterProxy의 역할은 끝난다.

강의에서 더 자세한 디버그를 찍어서 보면 WebSecurityConfgiuration 클래스에서  FilterChainProxy도 springSecurityFitlerChain 이름으로 등록하는 걸 볼 수 있다.

출처

https://www.inflearn.com/course/%EC%BD%94%EC%96%B4-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0/dashboard

 

스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 인프런 | 강의

초급에서 중.고급에 이르기까지 스프링 시큐리티의 기본 개념부터 API 사용법과 내부 아키텍처를 학습하게 되고 이를 바탕으로 실전 프로젝트를 완성해 나감으로써 스프링 시큐리티의 인증과

www.inflearn.com

 

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함