개발 기록

> [Spring Security] - Filter Chain 생성과 호출 본문

Spring

> [Spring Security] - Filter Chain 생성과 호출

1z 2023. 11. 13. 23:26

 

환경

java 17
spring version 6.0.11
spring boot version 3.1.3 

 

 

0. Spring Security FIlter 전체 흐름 

 


1. Spring Security Filter - DelegationFilterProxy

(1) DelegationgFilterProxy 는 무엇인가?

서블릿 필터를 구현한 Spring Bean에 모든 작업을 위임 하는 대리자
https://www.baeldung.com/spring-delegating-filter-proxy

 

 

▶배경:

서블릿 컨테이너는 자체 표준을 사용하여 필터 인스턴스를 등록할 수 있지만, Spring에서 정의한 Bean을 인식하지 못한다. 

 

원인:

Spring Bean은 스프링 컨테이너에서 생성 및 관리하는 컴포넌트들이고, ServletFilter는 서블릿 컨테이너에서 생성 및 관리하는 필터들이기 때문에 Spring Bean으로 Injection하거나 Spring에서 사용되는 기술을 Servlet에서 사용할 수 없다.

 

해결 : DelegatingFilterProxy 

서블릿 필터는 DelegatingFilterProxy 를 사용해서 서블릿 필터를 구현한 Spring Bean에 모든 작업을 위임 한다. 

 

 

(2) DelegationgFilterProxy 와 Spring Security

DelegatingFilterProxy는 "springSecurityFilterChain" 이름의 빈을 ApplicationContext 에서 찾아, 해당 빈에게 요청을 위임하는 역할을 수행한다. 이때 "springSecurityFilterChain"이라는 이름을 가진 빈이 바로 FilterChainProxy 이다.

그 후에 FilterChainProxy Bean에 요청을 전달한다. 이제 FilterChainProxy에서 필터들을 이용하여 보안처리를 진행한 후 최종적으로 SpringMVC의 DeispatcherServlet에 전달하여 요청에 대한 Servlet 처리를 하게 된다.

 

 

(3) DelegationgFilterProxy 는 어디서 등록되는가?

 

1. SecurityFilterAutoConfiguration
  • @CoditionalOnBean(name= DEFAULT_FILTER_NAME
    • springSecurityFilterChain 이라는 이름을 가진 Bean 이 존재한다면 DelegatingFilterProxyRegistrationBean을 bean으로 등록하는데 그때 DEFAULT_FILTER_NAME은 springSecurityFilterChain으로 전달 한다.

 

2. DelegatingFilterProxyRegistrationBean

☞ DelegatingFilterProxyRegistrationBean의 역할은 DelegatingFilterProxy를 등록하는 것이다.

DelegatingFilterProxyRegistrationBean에 getFilter가 호출될 때 DelegatingFilterProxy를 생성하게 된다.

 
 

 


2. Spring Security Filter - FilterChainProxy

 

(1) FilterChainProxy  무엇인가?

 

▶ SpringSecurityFilterChain의 이름으로 생성되는 필터 빈

  SecurityFilterChain을 순회하면서 제공된 URL(RequestMatcher)내용이 일치하는 필터체인을 반환하고 이를 doFilter를 통해 필터링을 진행한다

각 필터들을 순서대로 호출하며 인증/인가처리 및 각종 요청에 대한 처리를 수행한다

 

 

3. Spring Security Filter - FilterChainProxy 생성과정과 SecurityFilterChain 

(1) Class 다이어그램

 

 

(2) FilterChainProxy  생성 과정

1. WebSecurityConfiguration
  • springSecurityFilterChain Bean 등록
  • webSecurity.build() 메서드는 상속과 구현을 거쳐서, WebSecurity Class 의 performBuild 메서드를 호출하고,  FilterChainProxy의 생성은 performBuild 메서드 내부에서 이뤄진다.

 

2. WebSecurity 

  • SecurityFilterChain 을 매개변수로, FilterChainProxy 를 생성한다.
  • FilterChainProxy 는 Filter 목록을 가지게 된다.
 

 * FIlterChain

public class FilterChainProxy extends GenericFilterBean {

    private List<SecurityFilterChain> filterChains;

    public FilterChainProxy(SecurityFilterChain chain) {
       this(Arrays.asList(chain));
    }

    public FilterChainProxy(List<SecurityFilterChain> filterChains) {
       this.filterChains = filterChains;
    }

 

(3) SecurityFilterChain  생성 과정

* WebSecurity의 build()가 반환하는 것은 FilterChainProxy이며, HttpSecurity의 build()가 반환하는 것은 SecurityFilterChain 이다.

1. HttpSecurity

  • SecurityFilterChain 를 생성하여 WebSecurity에 전달한다.
  • HttpSecurity.build()부분을 따라가다보면 다음과같이 DefaultSecurityFilterChain을 반환해준다 즉 Bean으로 등록하는 SecurityFilterChain은 결국 DefaultSecrutiyFilterChain이다.

 

이렇게 만들어진 SecurityFilterChain은 WebSecurityConfiguration의 setFilterChains()로 주입된다.

 

2. HttpSecurity 기타 메서드 및  필드

☞ addFilter() :

Spring Security에서 제공하는 필터 중 하나의 인스턴스이거나 확장되어야 하는 필터를 추가한다.

 

filterOrders : 기본적으로 등록된 필터들이 순서대로 저장되어있기 때문에 필터체인의 순서를 가져오거나 확인하는데 사용된다.

private FilterOrderRegistration filterOrders = new FilterOrderRegistration();

 

@SuppressWarnings("serial")
final class FilterOrderRegistration {

    private static final int INITIAL_ORDER = 100;

    private static final int ORDER_STEP = 100;

    private final Map<String, Integer> filterToOrder = new HashMap<>();

    FilterOrderRegistration() {
       Step order = new Step(INITIAL_ORDER, ORDER_STEP);
       put(DisableEncodeUrlFilter.class, order.next());
       put(ForceEagerSessionCreationFilter.class, order.next());
       put(ChannelProcessingFilter.class, order.next());
       order.next(); // gh-8105
       put(WebAsyncManagerIntegrationFilter.class, order.next());
       put(SecurityContextHolderFilter.class, order.next());
       put(SecurityContextPersistenceFilter.class, order.next());
       put(HeaderWriterFilter.class, order.next());
       put(CorsFilter.class, order.next());
       put(CsrfFilter.class, order.next());
       put(LogoutFilter.class, order.next());
       this.filterToOrder.put(
             "org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
             order.next());
       this.filterToOrder.put(
             "org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter",
             order.next());
       put(X509AuthenticationFilter.class, order.next());
       put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
       this.filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order.next());
       this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
             order.next());
       this.filterToOrder.put(
             "org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter",
             order.next());
       put(UsernamePasswordAuthenticationFilter.class, order.next());
       order.next(); // gh-8105
       put(DefaultLoginPageGeneratingFilter.class, order.next());
       put(DefaultLogoutPageGeneratingFilter.class, order.next());
       put(ConcurrentSessionFilter.class, order.next());
       put(DigestAuthenticationFilter.class, order.next());
       this.filterToOrder.put(
             "org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter",
             order.next());
       put(BasicAuthenticationFilter.class, order.next());
       put(RequestCacheAwareFilter.class, order.next());
       put(SecurityContextHolderAwareRequestFilter.class, order.next());
       put(JaasApiIntegrationFilter.class, order.next());
       put(RememberMeAuthenticationFilter.class, order.next());
       put(AnonymousAuthenticationFilter.class, order.next());
       this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
             order.next());
       put(SessionManagementFilter.class, order.next());
       put(ExceptionTranslationFilter.class, order.next());
       put(FilterSecurityInterceptor.class, order.next());
       put(AuthorizationFilter.class, order.next());
       put(SwitchUserFilter.class, order.next());
    }
}

 

 

 

 


 

 

3. 기타 관련 Class

(1) HttpSecurityBuilder

SecurityBuilder는 아래와 같이 SecurityConfigurer를 포함하고 있으며,

인증 및 인가 초기화 작업은 SecurityBuilder 내부에서 SecurityConfigurer를 통해 진행된다.

 

public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
       extends SecurityBuilder<DefaultSecurityFilterChain> {
       
    <C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(Class<C> clazz);
.
.
.
    H authenticationProvider(AuthenticationProvider authenticationProvider);
    
    H userDetailsService(UserDetailsService userDetailsService) throws Exception;
    
    H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);
    
    H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);

    /**
     * <ul>
     * <li>{@link ForceEagerSessionCreationFilter}</li>
     * <li>{@link DisableEncodeUrlFilter}</li>
     * <li>{@link ChannelProcessingFilter}</li>
     * <li>{@link SecurityContextPersistenceFilter}</li>
     * <li>{@link LogoutFilter}</li>
     * <li>{@link X509AuthenticationFilter}</li>
     * <li>{@link AbstractPreAuthenticatedProcessingFilter}</li>
     * <li><a href="
     * {@docRoot}/org/springframework/security/cas/web/CasAuthenticationFilter.html">CasAuthenticationFilter</a></li>
     * <li>{@link UsernamePasswordAuthenticationFilter}</li>
     * <li>{@link org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter}</li>
     * <li>{@link org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter}</li>
     * <li>{@link ConcurrentSessionFilter}</li>
     * <li>{@link DigestAuthenticationFilter}</li>
     * <li>{@link BearerTokenAuthenticationFilter}</li>
     * <li>{@link BasicAuthenticationFilter}</li>
     * <li>{@link RequestCacheAwareFilter}</li>
     * <li>{@link SecurityContextHolderAwareRequestFilter}</li>
     * <li>{@link JaasApiIntegrationFilter}</li>
     * <li>{@link RememberMeAuthenticationFilter}</li>
     * <li>{@link AnonymousAuthenticationFilter}</li>
     * <li>{@link SessionManagementFilter}</li>
     * <li>{@link ExceptionTranslationFilter}</li>
     * <li>{@link FilterSecurityInterceptor}</li>
     * <li>{@link SwitchUserFilter}</li>
     * </ul>
     */
    H addFilter(Filter filter);

}

 

(2) HttpSecurityConfiguration

 

 

 


 

 

4. Security Filter 

 

SecurityContextPersistenceFilter SecurityContextRepository에서 SecurityContext를 가져오거나 저장하는 역할
SecurityContextHolderFilter SecurityContextRepository에서 SecurityContext를 로드하여 세션에 저장
SessionManagementFilter 다중 세션을 처리하거나 세션ID를 변경하는 등 세션 정책에 따라 세션을 처리하는 필터.
( 사용자와 관련된 모든 세션을 추적 )
LogoutFilter 설정된 로그아웃 URL로 오는 요청을 감시하며, 해당 유저를 로그아웃 처리
AuthenticationFilter 리소스에 대한 접근권한을 부여하는 인가필터
AuthorizationManager에게 인가 프로세스 위임
CORSFilter CORS정책 체크 필터, CorsProcessor를 호출하여 처리
CSRFilter CSRF공격 대응 필터.
ExceptionTranslationFilter  필터체인 내에서 발생한 예외를 잡아서 처리하는 필터 (401,403)
AnonymousAuthenticationFilter SecurityFilterChain의 마지막 인증필터로, 이 시점까지 인증을 시도하지 않았다면 익명 사용자 인증객체를 만들어 SecurityContext에 담아 SecurityContextHolder에 저장한다.
ConcurrentSessionFilter 동시세션을 처리 필터,
sessionRegistry에 같은 세션이 있는지 조회하여 동시세션을 찾고 세션을 갱신하거나 만료된 세션의 사용자를 로그아웃시킨다.
RememberMeAuthenticationFilter
세션이 만료되더라도 요청헤더에 포함된 remember-me토큰을 바탕으로 인증을 수행.
세션이 만료되어 SecurityContext 안의 Authentication객체가 null이 되면 필터가 동작
BasicAuthenticationFilter 
Basic 인증방식(ex. base64 인코딩)의 인증요청을 처리하는 인증필터 .
UsernamePasswordAuthenticationFilter
(아이디와 비밀번호를 사용하는 form 기반 인증)
설정된 로그인 URL로 오는 요청을 감시하며, 유저 인증 처리 (
AuthenticationManager를 통한 인증 실행)

 

RequestCacheAwareFilter : 로그인 성공 후, 원래 요청 정보를 재구성하기 위해 사용된다.
SecurityContextHolderAwareRequestFilter : 필터 체인상의 다음 필터들에게 부가정보를 제공한다.
FilterSecurityInterceptor : 이 필터는 AccessDecisionManager 로 권한부여 처리를 위임
WebAsyncManagerIntegrationFilter : 비동기 요청에서도 SecurityContext를 사용할 수 있도록 하는 필터
HeaderWriterFilter : 응답에 보안 관련 헤더를 추가하는 필터
AbstractPreAuthenticatedProcessingFilter : pre-authenticated된 인증요청을 처리하는 필터
DigestAuthenticationFilter : Digest 암호화 방식(ex. MD5, SHA)의 인증요청을 처리하는 인증필터

 

 

등등

 

 

 

 

참고

https://ttl-blog.tistory.com/1165#%F0%9F%A7%90%20SecurityBuilder%20%26%20SecurityConfigurer-1

https://kangwoojin.github.io/programing/spring-security-basic-filter-chain-proxy/#abstractsecuritywebapplicationinitializer

https://spring.io/guides/topicals/spring-security-architecture/