스프링 시큐리티 필터 구조

junto·2024년 4월 13일
0

spring

목록 보기
11/30
post-thumbnail

스프링 기본 필터

  • 스프링 시큐리티도 서블릿 필터로 구현되기에 먼저 기본 필터에 대해서 알아본다.
  • 요청이 서버에 도착하면 서블릿 컨테이너는 서버에 등록된 필터들로 필터 체인을 만든다. 요청은 먼저 모든 필터들을 거친 후에 DispatcherServelet에 의해 처리된다.
@Configuration
public class DebugConfig {

  @Autowired
  private ServletWebServerApplicationContext servletWebServerApplicationContext;

  @PostConstruct
  public void listServletFilters() {
    Map<String, ? extends FilterRegistration> filterRegistrations = servletWebServerApplicationContext.getServletContext().getFilterRegistrations();
    for (Map.Entry<String, ? extends FilterRegistration> entry : filterRegistrations.entrySet()) {
      System.out.println("Filter name: " + entry.getKey() + ", Filter class: " + entry.getValue().getClassName());
    }
  }
}

Spring Boot를 이용해 애플리케이션을 실행하면 다음과 같은 기본 필터를 볼 수 있다.

  • OrderedRequestContextFilter
  • WsFilter
  • OrderedCharacterEncodingFilter
  • OrderedFormContentFilter

1. OrderedRequestContextFilter

  • 현재 스레드에 요청을 노출하는 서블릿 필터이다. 이 필터는 타사 서블릿과의 통합을 위해 사용된다고 한다.
  • org.springframework.boot.web.servlet.filter
public class OrderedRequestContextFilter extends RequestContextFilter implements OrderedFilter { }

public class RequestContextFilter extends OncePerRequestFilter {
	...
    public void setThreadContextInheritable(boolean threadContextInheritable) {
		this.threadContextInheritable = threadContextInheritable;
	}
    
    protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
		initContextHolders(request, attributes);

		try {
			filterChain.doFilter(request, response);
		}
		finally {
			resetContextHolders();
			if (logger.isTraceEnabled()) {
				logger.trace("Cleared thread-bound request context: " + request);
			}
			attributes.requestCompleted();
		}
	}
    
    private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
		LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
		RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
		if (logger.isTraceEnabled()) {
			logger.trace("Bound request context to thread: " + request);
		}
	}
    ...
}
  • RequestContextHolder를 통해 요청에 RequestAttributes를 설정한다. 이를 통해 요청의 생명주기 동안 요청 데이터를 ThreadLocal에 저장하게 하여 요청을 처리하는 스레드에서 접근할 수 있도록 한다.
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        // This filter only needs to handle WebSocket upgrade requests
        if (!sc.areEndpointsRegistered() || !UpgradeUtil.isWebSocketUpgradeRequest(request, response)) {
            chain.doFilter(request, response);
            return;
        }

        // HTTP request with an upgrade header for WebSocket present
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // Check to see if this WebSocket implementation has a matching mapping
        String path;
        String pathInfo = req.getPathInfo();
        if (pathInfo == null) {
            path = req.getServletPath();
        } else {
            path = req.getServletPath() + pathInfo;
        }
        WsMappingResult mappingResult = sc.findMapping(path);

        if (mappingResult == null) {
            // No endpoint registered for the requested path. Let the
            // application handle it (it might redirect or forward for example)
            chain.doFilter(request, response);
            return;
        }

        UpgradeUtil.doUpgrade(sc, req, resp, mappingResult.getConfig(), mappingResult.getPathParams());
    }
}

2. WsFilter

  • 웹소켓 연결에 대한 초기 HTTP 연결을 처리하기 위한 필터이다. http 요청을 받아 웹 소켓으로 upgrade할지 판단한다. 웹 소켓으로 업그레이드 하는 요청이 아닌 경우 다음 필터를 실행하고, 그렇지 않다면 웹 소켓 프로토콜을 사용하기 위해 요청 정보를 매핑하여 업그레이드한다.
  • org.apache.tomcat.websocket.server.WsFilter
public class WsFilter extends GenericFilter {
	...
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        // This filter only needs to handle WebSocket upgrade requests
        if (!sc.areEndpointsRegistered() || !UpgradeUtil.isWebSocketUpgradeRequest(request, response)) {
            chain.doFilter(request, response);
            return;
        }

        // HTTP request with an upgrade header for WebSocket present
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // Check to see if this WebSocket implementation has a matching mapping
        String path;
        String pathInfo = req.getPathInfo();
        if (pathInfo == null) {
            path = req.getServletPath();
        } else {
            path = req.getServletPath() + pathInfo;
        }
        WsMappingResult mappingResult = sc.findMapping(path);

        if (mappingResult == null) {
            // No endpoint registered for the requested path. Let the
            // application handle it (it might redirect or forward for example)
            chain.doFilter(request, response);
            return;
        }

        UpgradeUtil.doUpgrade(sc, req, resp, mappingResult.getConfig(), mappingResult.getPathParams());
    }
}

3. OrderedCharacterEncodingFilter

  • 요청에 대한 문자 인코딩을 지정할 수 있는 서브렛 필터이다. 이를 이용하여 요청, 응답에 인코딩 형식을 일정하게 할 수 있다. 인코딩 설정이 있는지 확인하고, 이를 강제로 적용할지 옵션을 줄 수 있다(isForceRequestEncoding).
public class OrderedCharacterEncodingFilter extends CharacterEncodingFilter implements OrderedFilter { }

public class CharacterEncodingFilter extends OncePerRequestFilter {
	...
    @Override
	protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		String encoding = getEncoding();
		if (encoding != null) {
			if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
				request.setCharacterEncoding(encoding);
			}
			if (isForceResponseEncoding()) {
				response.setCharacterEncoding(encoding);
			}
		}
		filterChain.doFilter(request, response);
	}
}
  • org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter

4. OrderedFormContentFilter

  • 기본적으로 servlet 스펙에선 post 폼 데이터 요청을 자동으로 파싱한다. 해당 필터는 PUT, PATCH, DELETE 사용하는 HTTP 요청에 대해서도 폼 데이터를 올바르게 읽을 수 있도록 처리해주는 역할을 한다.
public class OrderedFormContentFilter extends FormContentFilter implements OrderedFilter {}

public class FormContentFilter extends OncePerRequestFilter {
public class FormContentFilter extends OncePerRequestFilter {

	private static final List<String> HTTP_METHODS = Arrays.asList("PUT", "PATCH", "DELETE");
	...
	@Override
	protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		MultiValueMap<String, String> params = parseIfNecessary(request);
		if (!CollectionUtils.isEmpty(params)) {
			filterChain.doFilter(new FormContentRequestWrapper(request, params), response);
		}
		else {
			filterChain.doFilter(request, response);
		}
	}

}
  • 요청이 PUT, PATCH, DELETE 메서드 중 하나를 사용하고 있고 적절한 콘텐트 타입을 가진 경우에 폼 데이터를 파싱한다.
private boolean shouldParse(HttpServletRequest request) {
		String contentType = request.getContentType();
		String method = request.getMethod();
		if (StringUtils.hasLength(contentType) && HTTP_METHODS.contains(method)) {
			try {
				MediaType mediaType = MediaType.parseMediaType(contentType);
				return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType);
			}
			catch (IllegalArgumentException ex) {
			}
		}
		return false;
}
  • org.springframework.boot.web.servlet.filter.OrderedFormContentFilter

스프링 시큐리티 필터

DelegatingFilterProxy와 SecurityFilterChain

  • 서블릿 컨테이너는 Filter자체로 인스턴스 등록을 할 수 있지만, Spring에서 정의한 Bean을 인식하지 못한다. 따라서 서블릿 컨테이너의 생명주기와 Spring의 ApplicationContext 사이를 연결하는 DelegatingFilterProxy라는 필터 구현을 제공한다.

  • Spring Security 종속성을 추가하게 되면, 사용자의 요청을 감시하기 위해 springSecurityFilterChain과 DelegatingFilterProxyRegistrationBean 이라는 필터가 추가되는 것을 확인할 수 있다.
public class FilterChainProxy extends GenericFilterBean {
	...
    public FilterChainProxy(SecurityFilterChain chain) {
		this(Arrays.asList(chain));
	}
	...
}
  • 그림과 같이 springSecurityFilterChain은 다양한 보안 필터로 구성되어 있다. FilterChainProxy는 SecurityFilterChain들 가지고 있는 Bean을 말한다.
public class DelegatingFilterProxyRegistrationBean extends AbstractFilterRegistrationBean<DelegatingFilterProxy> 		implements ApplicationContextAware {}

public abstract class AbstractFilterRegistrationBean<T extends Filter> extends DynamicRegistrationBean<Dynamic> {}

public class DelegatingFilterProxy extends GenericFilterBean {]

  • DelegatingFilterProxy를 이용해 Spring의 ApplicationContext에 등록된 springSecurityFilterChain에 필터 로직을 실행하게 된다.
  • DelegatingFilterProxyRegistrationBean은 스프링 Bean을 찾아 요청을 넘겨주는 서블릿 필터라고 볼 수 있다.

참고자료

profile
꾸준하게

0개의 댓글