https://docs.spring.io/spring-security/reference/servlet/architecture.html
Filter
Filter
를 기반으로 한다.Filter
의 역할은 request를 필터링하는 것FilterChain
Servlet
이 있다.Filter
들을 거(치면서 걸러내지는 작업을 거)쳐 Servlet
에 도달하는 것이다.Spring container는 FilterChain
을 생성하며(즉 FilterChain
도 Bean이다), FilterChain
은 다음을 포함한다.
Filter
Servlet
그리고 이 Filter
와 Servlet
은 request URI를 기반으로 HttpServletRequest
를 처리해야 한다.
Spring MVC application에서 Servlet
은 DispatcherServlet
의 instance다.
많아봤자 Servlet
한 개가 단일 HttpServletRequest
와 HttpServletResponse
만 handling할 수 있지만, 다음의 경우에서는 여러 개의 Filter
도 사용할 수 있다.
Filter
instance 또는 Servlet
instance가 호출되지 않도록 한다. 즉 뒤에 Filter나 Servlet이 더 있어도 현재 Filter 선에서 컷하고 응답할 수 있다(응답은 HttpServletResponse)HttpServletRequest
와 HttpServletResponse
는 downstream의 Filter
를 통해 수정된다.public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) {
// do something before the rest of the application
chain.doFilter(request, response); // invoke the rest of the application
// do something after the rest of the application
}
Filter
는 downstream의 Filter
, 그러니까 자기보다 밑에 있는(자신 다음의) Filter
또는 Servlet
에만 영향을 줄 수 있다.Filter
가 호출되는 순서가 매우매우 중요하다.DelegatingFilterProxy
Filter
의 구현체ApplicationContext
사이에 위치한다.Filter
instance 등록을 허용하지만, Spring-defined Bean은 인지할 수 없다.Filter
instance를 등록하려면 DelegatingFilterProxy
를 등록해야 한다.DelegatingFilterProxy
는 Servlet container 표준 mechanism을 통해 등록할 수 있지만, 모든 filtering 작업은 Filter
를 구현하는 Spring bean으로 위임(delegate)한다.요약하면 bean을 기준으로 필터링 하려면 Filter
를 구현체 Spring bean을 생성해야 하고, 이걸 FilterChain에 포함시키려면 DelegatingFilterProxy
를 통해 등록해야 Servlet Container가 Bean filter를 Filter instance로 인지할 수 있다는 얘기.
DelegatingFilterProxy
는 ApplicationContext
에서 를 찾아보고, 를 호출한다.
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) {
Filter delegate = getFilterBean(someBeanName);
delegate.doFilter(request, response);
}
Filter
를 lazy하게 얻는다.Filter
는 이미 Spring Bean으로 등록되어 있다.delegate
가 에 해당한다.DelegatingFilterProxy
를 쓰면Filter
bean instance를 찾는 것을 delay할 수 있다.Filter
를 등록해야 하기 때문ContextLoaderListener
를 사용하는 게 전형적이고, ContextLoaderListener
는 Filter bean 등록 전에 실행돼버린다.FilterChainProxy
DelegatingFilterProxy
는 하나의 Bean filter만 delegate할 수 있다.FilterChainProxy
는 다수의 Filter
instance를 delegate할 수 있다.Filter
instance는 SecurityFilterChain
을 통해 연결한다.SecurityFilterChain
FilterChainProxy
에 쓰인다.Filter
가 있을 때, 현재 요청에 대해 어느 Filter
가 호출되어야 하는지 결정하는 데에 쓰인다. 여기서 처음으로 match되는 SecurityFilterChain
하나만 호출된다./api/messages/
가 요청되면 과 모두 match되지만 먼저 match되는 하나만 호출된다./**
는 ANY니까). Filter
instance로 구성되고, 은 4개의 security Filter
instance로 구성된다는 것이다.SecurityFilterChain
은 unique하며 독립적으로 구성될 수 있다는 의미기 때문이다.Filter
instance를 갖지 않는 SecurityFilterChain
도 가능하다. 이건 Spring security가 그 요청을 무시하게 하기 위해 쓰인다.SecurityFilter
FilterChainProxy
에 SecurityFilterChain
을 통해 끼워진다.SecurityFilter
를 서로 다른 용도로 사용할 수 있다.Filter
는 정해진 순서로 호출된다.FilterOrderResgration
을 보면 된다.@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(Customizer.withDefaults())
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
위와 같이 구성하면 Filter
는 다음과 같은 순서로 구성된다.
CsrfFilter
Added by HttpSecurity#csrf
UsernamePasswordAuthenticationFilter
Added by HttpSecurity#formLogin
BasicAuthenticationFilter
Added by HttpSecurity#httpBasic
AutorizationFilter
Added by HttpSecurity#authorizeHttpRequests
실제로 내가 호출한 것과 다른 순서로 구성된다.
즉 내가 올바르게 호출하지 않아도 알잘딱깔센 해준다.
그냥 출력해서 확인하면 됨
SecurityFilter
출력하기2023-06-14T08:55:22.321-03:00 INFO 76975 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [
org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,
org.springframework.security.web.csrf.CsrfFilter@c29fe36,
org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,
org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,
org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]
Filter
목록을 볼 수 있다.Filter
가 호출되는지 확인하려고 쓴다.Filter
추가public class TenantFilter implements Filter {
@Override
public void doFilter(
ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain
) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String tenantId = request.getHeader("X-Tenant-Id");
boolean hasAccess = isUserAllowed(tenantId);
if (hasAccess) {
filterChain.doFilter(request, response);
return;
}
throw new AccessDeniedException("Access denied");
}
}
Filter
interface의 구현 클래스를 작성한다.AccessDeniedException
을 throw한다.Filter
구현하기 싫으면OncePerRequestFilter
와 같은 필터 클래스를 상속받으면 된다.doFilterInternal
메소드가 제공된다.HttpServletRequest
, HttpServletResponse
.doFilter
는 필터가 동작하게 하는 메소드고, doFilterInternal
은 doFilter
가 호출됐을 때 수행할 내용을 정의하는 메소드다.