디버깅으로 살펴보는 Spring Security 동작 과정

박민지·2023년 9월 2일
0

Spring Security

목록 보기
2/4

Spring Security가 적용된 Spring Boot 애플리케이션에서 api를 호출하고 디버깅하여 Spring Security가 요청을 처리하는 과정을 알아봅니다.
참고) Servlet 애플리케이션의 Spring Security(v 5.7.9)을 사용하였습니다.

1. 요청을 처리할 스레드를 실행합니다.


tomcat의 ThreadPool에서 요청을 처리할 스레드를 실행시킵니다.

2. tomcat의 소켓 처리 과정(락 획득, 소켓 이벤트 처리 등)을 수행합니다.

3. request의 기본 처리를 수행하고 tomcat의 valve로 요청을 넘겨줍니다.

Http11Processor 는 헤더를 파싱하고 HTTP의 기본적인 처리(HTTP 버전 업그레이드, maxKeepAliveRequests에 따른 keepAlive 처리 등)을 수행합니다.
CoyoteAdapter는 tomcate의 valve에게 요청에 대한 작업을 위임합니다.

참고) valve는 tomcat의 Catalina 컨테이너의 요청 처리 파이프라인 상에서 특정 기능을 수행하기 위해 추가될 수 있는 컴포넌트입니다.

4. tomcat의 valve를 수행합니다.

5 . ApplicationFilterChain을 통해 애플리케이션의 서블릿 필터들을 실행시킵니다.

Spring은 서블릿 필터에 등록할 수 있는 OncePerRequestFilter 추상 클래스를 제공합니다. 해당 클래스의 doFilterInternal()를 오버라이딩하여 서블릿 단에 원하는 필터를 등록할 수 있습니다.

사진에서 보이는 CharacterEncodingFilter, FormContentFilter, RequestContextFilter는 모두 OncePerRequestFilter를 상속한 클래스입니다.

CharacterEncodingFilter: 요청을 처리할 문자 인코딩을 지정할 수 있습니다.
FormContentFilter: HTTP PUT, PATH, DELETE 요청에 대한 form data를 파싱하여 Servlet 요청 파라미터로 노출합니다.
서블릿 스펙의 제한 사항(POST 요청만 form data를 파싱)을 해결합니다.
RequestContextFilter: HTTP 요청 객체를 해당 요청을 서비스하는 Thread에 바인딩합니다.

6. DelegatingFilterProxy를 실행합니다.


ApplicationFilterChaininternalDoFilter()를 통해 DelegatingFilterProxydoFilter()가 실행되고, doFilter() 내부에서 invokeDelegate()가 실행됩니다.

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

doFilter()에서는 위임할 객체(delegate)를 지연 초기화하고 invokeDelegate()를 실행합니다.
DelegatingFilterProxy.delegate 는 애플리케이션 시작 시간을 향상시키고, Spring의 open-in-view를 요청 범위 내로 한정하기 위해 지연 초기화됩니다.

protected void invokeDelegate(
			Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

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

DelegatingFilterProxy.invokeDelegate()delegate(FilterChainProxy)에게 모든 필터 처리를 위임합니다.
위 사진을 통해 delegateFilterChainProxy 객체임을 알 수 있습니다.

7. FilterChainProxySecurityFilterChain을 통해 Filter들을 실행시킵니다.


DelegatingFilterProxy.invokeDelegate()delegate.doFilter()를 통해 FilterChainProxy.doFilter()가 실행됩니다. FilterChainProxy.doFilter() 안에서는 doFilterInternal()을 실행하고, doFilterInternal() 안에서는 FilterChainProxy.VirtualFilterChain.doFilter()를 실행합니다.

    @Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
...
    		try {
    			request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
    			doFilterInternal(request, response, chain);
    		}
    		catch (Exception ex) {
...

FilterChainProxy.doFilter()에서는 try 구문의 doFilterInternal()을 실행합니다.

    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);
...
    		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;
    	}
...
/// FilterChainProxy.VirtualFilterChain.doFilter()
    @Override
    		public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
    			if (this.currentPosition == this.size) {
    				if (logger.isDebugEnabled()) {
    					logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
    				}
    				// Deactivate path stripping as we exit the security filter chain
    				this.firewalledRequest.reset();
    				this.originalChain.doFilter(request, response);
    				return;
    			}
    			this.currentPosition++;
    			Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
    			if (logger.isTraceEnabled()) {
    				logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
    						this.currentPosition, this.size));
    			}
    			nextFilter.doFilter(request, response, this);
    		}

doFilterInternal()에서는 Spring Security의 HttpFirewall 보안을 적용하여 요청을 wrapping하고 wrapping된 요청을 기반으로 적절한 SecurityFilterChain을 찾아 해당 SecurityFilterChain의 Filter들을 가져옵니다. 이후 FilterChainProxy의 static inner class인 VirtualFilterChain으로 Filter들을 하나씩 실행합니다.

0개의 댓글