Overview #
A Review Of Filters #
DelegatingFilterProxy #
package org.springframework.web.filter;
서블릿 컨테이너(Servet Conatiner) <-> 스프링 애플리케이션컨텍스트(ApplicationContext) 사이를 연결시켜주는(bridge) 역할을 한다.
- WebApplicationContext 에서 위임할 Filter(Bean)을 찾고,
- 해당 Filter 의
doFilter()
메서드를 호출한다.
- ‘서블릿 컨테이너’ 는 ‘스프링에 등록된 Bean’ 들을 사용(인식)하지 않는다.
- 따라서, (서블릿 컨테이너 표준에 맞게 등록된) DelegatingFilterProxy가 스프링에 등록된 Bean(단, Fiter 구현체)에 위임한다.
“(Servlet)Filter vs Bean Filter” 의 차이에 대해 이해할 것
public class DelegatingFilterProxy extends GenericFilterBean {
...
// [NOTE] WebApplicationContext 를 가지고 있다.
private WebApplicationContext webApplicationContext;
...
@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); // [NOTE] WebApplicationContext 로 부터 위임할 Filter 를 가져온다(찾는다).
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain); // [NOTE] work를 위임한다.
}
...
protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
}
FilterChainProxy #
package org.springframework.security.web;
Spring Security’s Servlet support is contained within FilterChainProxy
FilterChainProxy is a special Filter provided by Spring Security that allows delegating to many Filter instances through SecurityFilterChain
FilterChainProxy
는 Spring Security를 지원하기 위해 사용된다.FilterChainProxy
가SecurityFilterChain
의 다양한 Filter에 (work를)위임한다.

출처 : https://docs.spring.io/spring-security/reference/servlet/architecture.html
SecurityFilterChain #
package org.springframework.security.web;
SecurityFilterChain is used by FilterChainProxy to determine which Spring Security Filters should be invoked for this request.
// FilterChainProxy.java
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
...
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
...
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}


(In SecurityFilterChain)Security Filter 들은 (DelegatingFilterProxy가 아닌)FilterChainProxy에 관리된다.
이를 통해 얻는 이점은 다음과 같은 것들이 있다.
- Spring Security 관련 디버깅 시, FilterChainProxy를 starting point 로 사용할 수 있다.
- Spring Security 관련된 로직을 추가하고 싶다면, FilterChainProxy에 추가하면 된다. (무조건 실행되는, 핵심적인 부분이기 때문에)
추가적인 특징으로는 다음과 같은 것들이 있다.
- SecurityFilterChain 은 Security Filter 를 0~n 개 가질 수 있다.
- 또, 독립적인 여러 개의 SecurityFilterChain을 구성할 수 있다.
Security Filters #
Security Filter 의 종류는 대표적으로 다음과 같은 것들이 있다.
- UsernamePasswordAuthenticationFilter
- BasicAuthenticationFilter
- …
- ExceptionTranslationFilter
- FilterSecurityInterceptor
- …
Handling Security Exceptions #
ExceptionTranslationFilter
는 AccessDeniedException / AuthenticationException 을 HTTP Response 로 변환한다.
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
SecurityContextHolder.getContext().setAuthentication(null);
requestCache.saveRequest(request, response);
logger.debug("Calling Authentication entry point.");
authenticationEntryPoint.commence(request, response, reason);
}
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
sendStartAuthentication(request, response, chain, (AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
logger.debug("Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully uthenticated") + "); redirecting to authentication entry point", exception);
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource"))
);
}
else {
logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
accessDeniedHandler.handle(request, response, (AccessDeniedException) exception);
}
}
}
If the application does not throw an AccessDeniedException or an AuthenticationException, then ExceptionTranslationFilter does not do anything.
위 코드를 보면, 아래와 같이 호출되는 것을 알 수 있다.
AuthenticationException
->authenticationEntryPoint.commence()
AccessDeniedException
->accessDeniedHandler.handle()
그렇기에 AuthenticationEntryPoint, AccessDeniedHandler 를 재정의(상속)하여 커스텀하게 핸들링할 수 있다.
(error response, redirect to entry point 등)
...
http.exceptionHandling().accessDeniedHandler(new GPAccessDeniedHandler());
http.exceptionHandling().authenticationEntryPoint(new GPAuthenticationEntryPoint());
...
