Spring Security - 6. WebAsyncManagerIntegrationFilter.

하쮸·2025년 1월 28일

1. WebAsyncManagerIntegrationFilter.

  • WebAsyncManagerIntegrationFilter
    • Spring Security에서 중요한 역할을 하는 필터로, SecurityContext와 Spring Web의 WebAsyncManager를 통합함.
    • 서블릿에서 비동기 요청 처리(Async Processing)할 때 서블릿 입출력 쓰레드와 작업 쓰레드가 동일한 SecurityContextHolder의 SecurityContext를 참조할 수 있도록 도와줌.
      • 즉, SecurityContextHolder의 ThreadLocal 방식에 따라 동일한 쓰레드일 경우에만 SecurityContext에 접근할 수 있는데 비동기 방식의 경우 하나의 작업을 2개의 쓰레드로 수행하기 때문에 해당 부분을 보완해주기 위해서 필터가 존재함.
    • 비동기 요청 처리 시 SecurityContext의 일관성을 유지시켜줌.
    • SecurityContextCallableProcessingInterceptor를 사용하여 Callable에 SecurityContext를 설정함.

  • WebAsyncMagagerIntegrationFilter는 필터 단계에 존재하는데 어떻게 필터를 통과하고 컨트롤러 단계에서 발생하는 쓰레드 이동 문제를 처리할 수 있을까?
    • WebAsyncManagerIntegrationFilter가 수행하는 작업과 Callable의 동작 방식에 관련이 있음.
package org.springframework.security.web.context.request.async;

import java.io.IOException;
import java.util.concurrent.Callable;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.util.Assert;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * Provides integration between the {@link SecurityContext} and Spring Web's
 * {@link WebAsyncManager} by using the
 * {@link SecurityContextCallableProcessingInterceptor#beforeConcurrentHandling(org.springframework.web.context.request.NativeWebRequest, Callable)}
 * to populate the {@link SecurityContext} on the {@link Callable}.
 *
 * @author Rob Winch
 * @see SecurityContextCallableProcessingInterceptor
 */
public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {

	private static final Object CALLABLE_INTERCEPTOR_KEY = new Object();

	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
		.getContextHolderStrategy();

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
			.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
		if (securityProcessingInterceptor == null) {
			SecurityContextCallableProcessingInterceptor interceptor = new SecurityContextCallableProcessingInterceptor();
			interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
			asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY, interceptor);
		}
		filterChain.doFilter(request, response);
	}

	/**
	 * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
	 * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
	 *
	 * @since 5.8
	 */
	public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
		Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
		this.securityContextHolderStrategy = securityContextHolderStrategy;
	}

}
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY, interceptor);
  • doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 메서드.
    • 현재 요청의 WebAsyncManager를 가져옴.
      • 현재 요청에서 SecurityContextCallableProcessingInterceptor를 확인하고 이미 존재하면 그대로 사용.
      • 없으면 새로 생성하여 WebAsyncManager에 등록.
    • 필터 체인을 계속 실행하여 요청을 계속 처리함.

  • 클라이언트쪽에서 보낸 요청이 필터를 모두 거친 후 스프링 컨테이너에 있는 DispatcherServlet이 해당 요청에 알맞는 Controller 메서드를 찾아 요청하고 Callable을 반환 받음.
  • DispatcherServlet이 Callable을 비동기 처리하도록 WebAsyncManager에 요청함.
  • WebAsyncManager는 Callable을 특정 TaskExecutor에 할당하여 비동기를 처리함.
  • Callable의 작업이 완료되면 WebAsyncManager로 결과값을 리턴해줌.
  • WebAsyncManager는 DispatcherServlet에게 Dispatch 요청을 함.
  • DispatcherServlet은 WebAsyncManager에게서 결과값을 받은 다음 적절하게 처리하여 응답을 반환함.
  • WebAsyncManager는 WebAsyncManagerIntegrationFilter를 통해 기존 쓰레드가 참조하던 SecurityContext를 전달받고 SecurityContextCallableProcessingInterceptor를 활용하여 새로운 쓰레드에서도 기존 SecurityContext를 사용할 수 있음.
    • 따라서 쓰레드가 변경되더라도 SecurityContext에서 동일한 값을 획득할 수 있음.
profile
Every cloud has a silver lining.

0개의 댓글