스프링 시큐리티를 사용하면 아래와 같이 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);
}
}
}
}