SecurityFilterChain 생성 및 동작 원리

박진선·2023년 6월 25일
0
post-custom-banner

스프링 시큐리티를 사용하면 아래와 같이 HttpSecurity 를 사용하여 간단한 설정을 통해 SecurityFilterChain 을 Bean 으로 등록해 원하는 Filter 가 애플리케이션 API 요청시 마다 실행되는데 어떠한 방식으로 SecurityFilterChain 생성, 동작 하는지 디버깅을 통해 알아보고자 한다.

우선 HttpSecurity 가 어떻게 생성되는지 부터 알아보자 아래의 httpSecurity() 메소드를 보면 HttpSecurity 인스턴스를 생성하여 각종 기본 시큐리티 설정 후에 반환 하여 Bean 으로 등록된다.

@Configuration(proxyBeanMethods = false)
class HttpSecurityConfiguration {
	
    ...
    ...
    
    @Bean(HTTPSECURITY_BEAN_NAME)
	@Scope("prototype")
	HttpSecurity httpSecurity() throws Exception {
		WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
				this.context);
		AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
				this.objectPostProcessor, passwordEncoder);
		authenticationBuilder.parentAuthenticationManager(authenticationManager());
		authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
		HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
		// @formatter:off
		http
			.csrf(withDefaults())
			.addFilter(new WebAsyncManagerIntegrationFilter())
			.exceptionHandling(withDefaults())
			.headers(withDefaults())
			.sessionManagement(withDefaults())
			.securityContext(withDefaults())
			.requestCache(withDefaults())
			.anonymous(withDefaults())
			.servletApi(withDefaults())
			.apply(new DefaultLoginPageConfigurer<>());
		http.logout(withDefaults());
		// @formatter:on
		applyDefaultConfigurers(http);
		return http;
	}
}

아래 로직은 HttpSecurity 를 활용하여 추가적인 보안 설정을 추가했다. HttpSecurity 의 authorizeHttpRequests() 메소드를 호출하게 될 경우 어떠한 로직이 실행되는지 확인 해보자.

  @Bean
  SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
      .and()
      .httpBasic().disable()
      .formLogin().disable()
      .csrf().disable()
      .headers().frameOptions().sameOrigin()
      .and()
      .authorizeHttpRequests()
      .antMatchers(HttpMethod.GET,"/members").hasRole("USER")
      .antMatchers(HttpMethod.DELETE,"/members").hasRole("USER")
      
    return http.build();
  }

아래 로직에서 authorizeHttpRequests() 메소드를 보면 getOrApply() 호출하면서 AuthorizeHttpRequestsConfigurer 인스턴스를 생성하여 생성자 인자로 넘겨 준다.

getOrApply 메소드 내부에서는 부모 클래스인 AbstractConfiguredSecurityBuilder 의 getConfigurer 메소드를 호출 해 configurers 필드에 이미 AuthorizeHttpRequestsConfigurer 가 저장되어 있는지 여부를 판단하고 아니라면 부모 클래스의 apply 메소드를 호출하여 인자로 넘겨준다.

apply 메소드 내에서는 파라미터로 받은 AuthorizeHttpRequestsConfigurer 에 몇가지 설정을 추가하고 add() 메소드를 호출하여 인자로 넘겨준다.

add 메소드는 List<SecurityConfigurer<O, B>> configs 지역 변수를 생성하여 Configurer 를 저장하고 configurers 필드에 AuthorizeHttpRequestsConfigurer 클래스 정보와 configs지역 변수를 저장한다.

add 메소드 로직이 종료되고 다시 apply 메소드로 돌아가서 AuthorizeHttpRequestsConfigurer 클래스가 authorizeHttpRequests 메소드 까지 반환되고
AuthorizeHttpRequestsConfigurer 의 getRegistry 메소드를 호출하여 registry 필드를 authorizeHttpRequests 메소드를 호출한 쪽에 반환하게 되고 HTTP Request URL 에 접근 권한을 설정하는 로직이 시작된다.

중요한 것은 HttpSecurity 설정을 통해 생성된 모든 XXConfigurer 클래스는 동일한 방식으로 AbstractConfiguredSecurityBuilder 클래스의 configurers 필드에 대입되는 것이다.

위 설정에서 마지막 http.build() 메소드를 호출하면 부모 클래스 AbstractSecurityBuilder의 build() 메소드가 호출되고 해당 메소드에서 AbstractConfiguredSecurityBuilder 의 doBuild() 메소드를 호출한다.

doBuild() 메소드를 보면 init(), configure() 메소드를 호출한다.
두 메소드 로직에는 getConfigurers 메소드를 호출하여 configurers 필드에 저장된 value 인 List<SecurityConfigurer> 를 반환 받아 저장된 XXConfigurer 의 init(), configure() 를 호출하여 초기화 및 Filter 를 생성한다. 생성된 Filter 는 HttpSecurity 의 addFilter 메소드를 호출하여 인자로 넘겨진다.

addFilter 메소드를 보면 filterOrders 필드의 getOrder 메소드를 호출하면서 Filter 의 클래스 정보를 인자로 넘겨주어 해당 필터의 실행 순서를 반환 받는다. 다음 저장할 Filter 와 실행 순서를 OrderedFilter 로 감싸서 filters 필드에 저장한다

HttpSecurity 의 filterOrders 필드를 보면 new FilterOrderRegistration(); 인스턴스를 생성하여 명시적 초기화를 선언했다. 해당 클래스의 생성자에서 filterToOrder 필드 에 각 Filter 의 클래스명과 실행 순서가 대입되어 getOrder 메소드를 호출하면 filterToOrder 필드에서 실행 순서를 반환한다.

이제 doBuild 메소드에서 performBuild() 메소드를 호출하면 HttpSecurity 의 performBuild() 가 호출된다.

해당 메소드에서는 filters 필드에 저장된 Filter 들을 실행 순서에 맞게 정렬하고 DefaultSecurityFilterChain 인스턴스를 생성하면서 인자 로 전달 후 http.build() 를 호출한 스택 까지 반환되어 Bean 으로 등록된다.

public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
		implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
    private final RequestMatcherConfigurer requestMatcherConfigurer;

	private List<OrderedFilter> filters = new ArrayList<>();

	private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;

	private FilterOrderRegistration filterOrders = new FilterOrderRegistration();

	private AuthenticationManager authenticationManager;
    
    ...
    ...
    ...
    
    public AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry authorizeHttpRequests()
			throws Exception {
		ApplicationContext context = getContext();
		return getOrApply(new AuthorizeHttpRequestsConfigurer<>(context)).getRegistry();
	}
    
    private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)
			throws Exception {
		C existingConfig = (C) getConfigurer(configurer.getClass());
		if (existingConfig != null) {
			return existingConfig;
		}
		return apply(configurer);
	}
    
    public HttpSecurity addFilter(Filter filter) {
		Integer order = this.filterOrders.getOrder(filter.getClass());
		if (order == null) {
			throw new IllegalArgumentException("The Filter class " + filter.getClass().getName()
					+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
		}
		this.filters.add(new OrderedFilter(filter, order));
		return this;
	}
    
    protected DefaultSecurityFilterChain performBuild() {
		ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(
				ExpressionUrlAuthorizationConfigurer.class);
		AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);
		boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;
		Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent,
				"authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");
		this.filters.sort(OrderComparator.INSTANCE);
		List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
		for (Filter filter : this.filters) {
			sortedFilters.add(((OrderedFilter) filter).filter);
		}
		return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
	}
    
}


public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
		extends AbstractSecurityBuilder<O> {
        
    private final Log logger = LogFactory.getLog(getClass());

	private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();

	private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing = new ArrayList<>();

	private final Map<Class<?>, Object> sharedObjects = new HashMap<>();

	private final boolean allowConfigurersOfSameType;

	private BuildState buildState = BuildState.UNBUILT;

	private ObjectPostProcessor<Object> objectPostProcessor;
    
    ...
    ...
    ...
    
    public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
		configurer.addObjectPostProcessor(this.objectPostProcessor);
		configurer.setBuilder((B) this);
		add(configurer);
		return configurer;
	}
    
    private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
		Assert.notNull(configurer, "configurer cannot be null");
		Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
				.getClass();
		synchronized (this.configurers) {
			if (this.buildState.isConfigured()) {
				throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
			}
			List<SecurityConfigurer<O, B>> configs = null;
			if (this.allowConfigurersOfSameType) {
				configs = this.configurers.get(clazz);
			}
			configs = (configs != null) ? configs : new ArrayList<>(1);
			configs.add(configurer);
			this.configurers.put(clazz, configs);
			if (this.buildState.isInitializing()) {
				this.configurersAddedInInitializing.add(configurer);
			}
		}
	}
    
    protected final O doBuild() throws Exception {
		synchronized (this.configurers) {
			this.buildState = BuildState.INITIALIZING;
			beforeInit();
			init();
			this.buildState = BuildState.CONFIGURING;
			beforeConfigure();
			configure();
			this.buildState = BuildState.BUILDING;
			O result = performBuild();
			this.buildState = BuildState.BUILT;
			return result;
		}
	}
    
	private void init() throws Exception {
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
		for (SecurityConfigurer<O, B> configurer : configurers) {
			configurer.init((B) this);
		}
		for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) {
			configurer.init((B) this);
		}
	}

	private void configure() throws Exception {
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
		for (SecurityConfigurer<O, B> configurer : configurers) {
			configurer.configure((B) this);
		}
	}

	private Collection<SecurityConfigurer<O, B>> getConfigurers() {
		List<SecurityConfigurer<O, B>> result = new ArrayList<>();
		for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
			result.addAll(configs);
		}
		return result;
	}
}

public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
	private O object;

    public final O build() throws Exception {
            if (this.building.compareAndSet(false, true)) {
                this.object = doBuild();
                return this.object;
            }
            throw new AlreadyBuiltException("This object has already been built");
        }


}

여기까지 HttpSecurity 를 활용하여 SecurityFilterChain 을 생성하는 원리에 대해 알아보았다. 다음은 Bean 으로 등록된 SecurityFilterChain 이 어떻게 Http Request 마다 동작하는지에 대해 알아보도록 하자.

우선 FilterChainProxy 의 생성 과정을 알기 위해 WebSecurity 의 생성과정에 대해 파헤처 보자

아래 로직을 보면 securityFilterChains 필드는 @Autowired 가 선언된 setFilterChains 메소드를 통해 Setter 주입 되어 우리가 HttpSecurity 를 설정하여 생성한 SecurityFilterChain 이 @Autowired 대입된다.

springSecurityFilterChain 메소드 에서 몇가지 로직을 지나 SecurityFilterChain 을 webSecurity.addSecurityFilterChainBuilder() 메소드를 통해 webSecurity 의 securityFilterChainBuilders 필드에 대입시킨다.

webSecurity.build() 메소드를 호출하여 HttpSecurity 의 build() 메소드를 호출했을 때와 같은 로직이 실행되고 WebSecurity 의 performBuild() 메소드를 호출한다.

해당 메소드 에서 지역 변수 List<SecurityFilterChain> securityFilterChains 생성하여 securityFilterChainBuilders 필드에서 SecurityFilterChain 을 가져와 대입한다.
FilterChainProxy 인스턴스를 생성하여 생성자 인자로 넘겨주어 해당 클래스 filterChains 필드에 대입된다.

이제 생성된 filterChainProxy 가 몇가지 로직을 거친 후 webSecurity.build() 를 호출한 스택까지 반환되어 Bean 으로 등록된다.

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {

	private WebSecurity webSecurity;

	private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();
    
    ...
    ...
    
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
		boolean hasFilterChain = !this.securityFilterChains.isEmpty();
		Assert.state(!(hasConfigurers && hasFilterChain),
				"Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
		if (!hasConfigurers && !hasFilterChain) {
			WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			this.webSecurity.apply(adapter);
		}
		for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
			this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
			for (Filter filter : securityFilterChain.getFilters()) {
				if (filter instanceof FilterSecurityInterceptor) {
					this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
					break;
				}
			}
		}
		for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
			customizer.customize(this.webSecurity);
		}
		return this.webSecurity.build();
	}
    
    @Autowired(required = false)
	void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
		this.securityFilterChains = securityFilterChains;
	}

}

public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
		implements SecurityBuilder<Filter>, ApplicationContextAware, ServletContextAware {
        
        ...
        ...
        
        protected Filter performBuild() throws Exception {
		Assert.state(!this.securityFilterChainBuilders.isEmpty(),
				() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
						+ "Typically this is done by exposing a SecurityFilterChain bean. "
						+ "More advanced users can invoke " + WebSecurity.class.getSimpleName()
						+ ".addSecurityFilterChainBuilder directly");
		int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
		List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
		for (RequestMatcher ignoredRequest : this.ignoredRequests) {
			WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
					+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
			SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
			securityFilterChains.add(securityFilterChain);
			requestMatcherPrivilegeEvaluatorsEntries
					.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
		}
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
			SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
			securityFilterChains.add(securityFilterChain);
			requestMatcherPrivilegeEvaluatorsEntries
					.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
		}
		if (this.privilegeEvaluator == null) {
			this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
					requestMatcherPrivilegeEvaluatorsEntries);
		}
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		if (this.httpFirewall != null) {
			filterChainProxy.setFirewall(this.httpFirewall);
		}
		if (this.requestRejectedHandler != null) {
			filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
		if (this.debugEnabled) {
			this.logger.warn("\n\n" + "********************************************************************\n"
					+ "**********        Security debugging is enabled.       *************\n"
					+ "**********    This may include sensitive information.  *************\n"
					+ "**********      Do not use in a production system!     *************\n"
					+ "********************************************************************\n\n");
			result = new DebugFilter(filterChainProxy);
		}
		this.postBuildAction.run();
		return result;
	}
        
}

이제 Bean 으로 등록된 FilterChianProxy 가 어떻게 HTTP Request 마다 실행되는지 알아보자

ApplicationFilterChain 에서 DelegatingFilterProxy 의 doFilter() 메소드를 호출한다.

해당 메소드에서 webApplicationContext 필드에 저장된 AnnotationConfigServletWebServerApplicationContext 를 가져온 뒤 initDelegate() 메소드를 호출하여 인자로 넘긴다.

initDelegate 메소드 에서 getTargetBeanName() 메소드를 호출하여 targetBeanName 필드를 가져온다. 해당 필드에는 "springSecurityFilterChain" 문자열이 대입되어 있다. getBean() 메소드를 호출하여 Bean 이름과, Filter 클래스타입을 인자로 넘겨 FilterChainProxy를 가져온 뒤 반환한다.

다시 doFilter 로 돌아와서 invokeDelegate 메소드를 호출하면서 인자로 FilterChainProxy 등등을 넘겨준다.

해당 메소드에서 FilterChainProxy의 doFilter 메소드를 호출한다.

public class DelegatingFilterProxy extends GenericFilterBean {
  @Nullable
  private String contextAttribute;
  @Nullable
  private WebApplicationContext webApplicationContext;
  @Nullable
  private String targetBeanName;
  private boolean targetFilterLifecycle;
  @Nullable
  private volatile Filter delegate;
  private final Object delegateMonitor;

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    Filter delegateToUse = this.delegate;
    if (delegateToUse == null) {
      synchronized(this.delegateMonitor) {
        delegateToUse = this.delegate;
        if (delegateToUse == null) {
          WebApplicationContext wac = this.findWebApplicationContext();
          if (wac == null) {
            throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
          }

          delegateToUse = this.initDelegate(wac);
        }

        this.delegate = delegateToUse;
      }
    }

    this.invokeDelegate(delegateToUse, request, response, filterChain);
  }

  protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    String targetBeanName = this.getTargetBeanName();
    Assert.state(targetBeanName != null, "No target bean name set");
    Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
    if (this.isTargetFilterLifecycle()) {
      delegate.init(this.getFilterConfig());
    }

    return delegate;
  }

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

}

FilterChainProxy 의 doFilter 메소드가 호출되면 몇가지 로직을 지나 doFilterInternal() 메소드를 호출한다.

해당 메소드에서 getFirewalledRequest 메소드를 호출하여 ServletRequest 를 인자로 넘겨 허용된 HttpMethod 인지 URL에 허용되지 않은 요소가 있는지 검증하는 로직을 거쳐 StrictFirewalledRequest 를 반환한다. Response 는 FirewalledResponse 를 반환한다.
VirtualFilterChain 인스턴스를 생성하면서 생성자로 filterChains 필드 에서 Filter 목록을 가져와 넘겨준다.
생성한 VirtualFilterChain의 doFilter 메소드를 호출한다. VirtualFilterChain 은 FilterChainProxy 의 정적 멤버 클래스이다.

currentPosition 필드는 현재 포지션이 저장되고 additionalFilters 필드에는 실행시킬 Filter 목록이 순번에 맞게 정렬되어 저장되어 있다. additionalFilters 필드에서 currentPosition 필드에 맞는 Filter 를 가져온뒤 doFilter 메소드를 호출해 HttpSecurity 설정을 통해 생성된 Filter 들을 하나씩 실행시킨다. doFilter 메소드의 FilterChain 파라미터의 인자로 this 를 넘겨주어 Filter 가 실행되고 다시 VirtualFilterChain의 doFilter 메소드가 호출되어 실행시킬 Filter의 size 만큼 반복이 가능하다.

public class FilterChainProxy extends GenericFilterBean {

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
    if (!clearContext) {
      this.doFilterInternal(request, response, chain);
    } else {
      try {
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
        this.doFilterInternal(request, response, chain);
      } catch (Exception var11) {
        Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var11);
        Throwable requestRejectedException = this.throwableAnalyzer.getFirstThrowableOfType(RequestRejectedException.class, causeChain);
        if (!(requestRejectedException instanceof RequestRejectedException)) {
          throw var11;
        }

        this.requestRejectedHandler.handle((HttpServletRequest)request, (HttpServletResponse)response, (RequestRejectedException)requestRejectedException);
      } finally {
        SecurityContextHolder.clearContext();
        request.removeAttribute(FILTER_APPLIED);
      }

    }
  }
  
  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 = this.getFilters((HttpServletRequest)firewallRequest);
    if (filters != null && filters.size() != 0) {
      if (logger.isDebugEnabled()) {
        logger.debug(LogMessage.of(() -> {
          return "Securing " + requestLine(firewallRequest);
        }));
      }

      VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
      virtualFilterChain.doFilter(firewallRequest, firewallResponse);
    } else {
      if (logger.isTraceEnabled()) {
        logger.trace(LogMessage.of(() -> {
          return "No security for " + requestLine(firewallRequest);
        }));
      }

      firewallRequest.reset();
      chain.doFilter(firewallRequest, firewallResponse);
    }
  }
  
  private static final class VirtualFilterChain implements FilterChain {
    private final FilterChain originalChain;
    private final List<Filter> additionalFilters;
    private final FirewalledRequest firewalledRequest;
    private final int size;
    private int currentPosition;
    
    private VirtualFilterChain(FirewalledRequest firewalledRequest, FilterChain chain, List<Filter> additionalFilters) {
      this.currentPosition = 0;
      this.originalChain = chain;
      this.additionalFilters = additionalFilters;
      this.size = additionalFilters.size();
      this.firewalledRequest = firewalledRequest;
    }
    
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
      if (this.currentPosition == this.size) {
        if (FilterChainProxy.logger.isDebugEnabled()) {
          FilterChainProxy.logger.debug(LogMessage.of(() -> {
            return "Secured " + FilterChainProxy.requestLine(this.firewalledRequest);
          }));
        }

        this.firewalledRequest.reset();
        this.originalChain.doFilter(request, response);
      } else {
        ++this.currentPosition;
        Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
        if (FilterChainProxy.logger.isTraceEnabled()) {
          FilterChainProxy.logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(), this.currentPosition, this.size));
        }

        nextFilter.doFilter(request, response, this);
      }
    }
    
  
  }

}
profile
주니어 개발자 입니다
post-custom-banner

0개의 댓글