스프링은 많은 필터들을 제공하는데 그 중에서도 DelegatingFilterProxy가 시큐리티 필터를 제공한다.
FilterChainProxy
Spring Security의 구현은 서블릿 필터 (javax.servlet.Filter 인터페이스 구현체) 를 통해 이뤄진다.
WebSecurityConfigurerAdapter라는 추상 클래스를 상속받는 구현체에서 설정할 수 있다.(@EnableWebSecurity 함께 사용)
그 구현체가 바로 DelegatingFilterProxy이 되는 것이다.
SecurityFilterAutoConfiguration 클래스에서 DelegatingFilterProxy를 빈으로 자동 등록시킨다.
@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
FilterChainProxy의 핵심 메소드
- doFilter
- doFilterInternal
- getFilters
doFilter
1. 모든 요청은 FilterChainProxy를 거치며 그 안에는 doFilter를 거치게 된다.
2. doFilter는 doFilterInternal를 호출한다.
doFilterInternal
1. GetFilters를 호출해 요청을 처리할 Filter들을 불러온다.
2. 적절한 Filter를 찾지 못하면 종료된다.
3. 찾은 필터들을 VirtualFilterChain에게 위임한다.
VirtualFilterChain - doFilterInternal 로부터 위임받은 Filter들을 순서대로 실행 시켜주는 역할
getFilters
request나 요청 url을 통해 SecurityFilterChain 목록을 가져온다.
DelegatingFilterProxy 내부 동작 코드
Target Bean을 지정해야 하는데 최상단의 그림에서 알 수 있듯 그 빈이 바로 FilterChainProxy를 가리킨다.
DelegatingFilterProxy는 invokeDelegate 메서드를 통해 단순히 FilterChainProxy의 dofilter를 호출함을 알 수 있다.
그러면 FilterChainProxy의 내부 동작은 어떻게 될까?
@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);
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
FilterChainProxy 내부 동작 코드
doFilter를 호출하게 되고 doFilter는 doFilterInternal를 호출하게 된다. 이 곳에서 실질적인 filter 로직이 실행되게 되는 것이다.
getFilters에서 스프링 시큐리티의 필터 목록을 가져오게 된다. 필터 목록을 가져온 후에 차례대로 VirtualFilterChain을 통해 필터링을 수행하게 된다.
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 (RequestRejectedException var9) {
this.requestRejectedHandler.handle((HttpServletRequest)request, (HttpServletResponse)response, var9);
} 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);
}));
}
FilterChainProxy.VirtualFilterChain virtualFilterChain = new FilterChainProxy.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 List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
Iterator var3 = this.filterChains.iterator();
SecurityFilterChain chain;
do {
if (!var3.hasNext()) {
return null;
}
chain = (SecurityFilterChain)var3.next();
if (logger.isTraceEnabled()) {
++count;
logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, count, this.filterChains.size()));
}
} while(!chain.matches(request));
return chain.getFilters();
}
'스프링 부트 > 시큐리티' 카테고리의 다른 글
[스프링 시큐리티] 주요 Security Filter들 정리 (0) | 2022.05.24 |
---|---|
[스프링 시큐리티] SecurityContextHolder (0) | 2022.05.23 |
[스프링 시큐리티] AuthenticationManager, AccessDecisionManager (0) | 2022.05.23 |
[스프링 시큐리티] PasswordEncoder (0) | 2022.05.23 |
댓글