Spring Security : 아키텍쳐 / 필터 초기화와 보안 클래스 다중 설정

    이 게시글은 인프런 정수원님의 강의를 복습하며 작성한 글입니다.

    여러 보안 클래스 설정

    지금까지는 하나의 보안 설정 클래스만 작성해서 사용하는 경우를 다뤄왔다. 그런데 보안을 고려해야할 것이 많아지면, 당연히 하나의 보안 설정 클래스에서 너무 많은 책임을 지게 된다. 따라서, 설정 클래스의 분리를 통해 코드의 복잡도를 낮출 것이 고려된다. 그렇다면, 이렇게 설정 클래스를 여러 개로 나누어 설정을 하는 경우는 어떻게 동작을 하게 될까? 

     

    여러 보안 클래스 설정, 그림

    다음과 같이 두 개의 설정 클래스가 있을 때를 가정해보자. API는 동일한데, 단순히 antMatcher가 다른 경우로 이해를 할 수 있다. 위 경우 어떻게 동작할까? 

    • 설정 클래스 별로 보안 기능이 각각 동작함.
    • 설정 클래스 별로 각각 필터가 생성된다. 
    • 설정 클래스 별로 가지고 있는 requestMatcher를 통해 Spring Security는 보안 설정을 처리할 필터를 선택한다.

    위에서 볼 수 있듯이, 설정 클래스의 갯수만큼 FilterChain이 생성되고, 생성된 FilterChian은 FilterChainProxy에 저장된다. 그리고, FilterChainProxy에서 요청온 URL을 바탕으로 antMatcher를 확인해 필요한 FilterChain을 선택해서 보안 처리를 해준다. 

     

    Spring Security의 다중 보안 필터 동작 방식

    1. 사용자로부터 요청이 오면 DelegatingFilterProxy로 요청이 온다.
    2. DelegatingFilterProxy는 위임할 필터를 스프링 컨테이너에서 찾아온다. 이 때, springSecurityFilterChain 이름으로 등록된 빈을 찾아와, 해당 빈에 인증/인가 처리를 위임한다.
    3. 이 빈은 FilterChainProxy다. FilterChainProxy는 사용자로부터 요청을 받는다.
    4. FilterChainProxy는 내부적으로 FilterChains를 가지고 있으며, 여기서 사용자로부터 요청 온 URL과 각 FilterChain들의 antMatcher를 확인해서 매칭되는 FilterChain을 선택해서 인증/인가처리를 해준다. 

     

     

    다중 설정 시, 시큐리티의 초기화 + 동작

     

     

    Spring Security 초기화 과정.

    WebSecurity

    서버를 기동하면 WebSecurity 클래스로 넘어온다. 이 클래스에서 FilterChainProxy를 생성하면서 SecurityFilterChains를 넘겨주는 것을 볼 수 있다. 

    이 때, 생성자로 넘겨주는 securityFilterChains를 살펴보면 앞에서 설정한 API + 순서대로 만들어진 것을 볼 수 있다. 이 securityFilterChains를 FilterChainProxy에 넘겨주기 때문에, 각 보안 설정 클래스에 따라 우리가 선택적으로 인증 / 인가 처리를 선택할 수 있게 되는 것이다. 

     

    다중 설정 시, 동작 방식 + 코드 실습

    @Configuration
    @EnableWebSecurity 
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http
                    .antMatcher("/admin/**")
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .httpBasic();
    
            SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    
        }
    }
    
    @Configuration
    class MySecurityConfig2 extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    .anyRequest()
                    .permitAll()
                    .and()
                    .formLogin();
        }
    
    }

    먼저 위 코드를 이용해서 스프링 부트를 실행해보았다.

    실행했을 때, WebSecurityConfiguration 클래스에서 Order와 관련된 Exception이 발생하는 것을 확인할 수 있다. Exception이 발생한 이유는 간단하다. 위의 안내문처럼 순서가 정해져있지 않기 때문이다. 현재 위에는 2개의 설정 클래스가 있는데, 위 설정 클래스는 각각 FilterChain으로 바뀌고, FilterChainProxy 내부의 FilterChains에 순서대로 저장된다.

    그런데 문제는 FilterChains에 저장될 순서가 정해져있지 않다는 것이다. 따라서 FilterChains에 저장될 FilterChain들의 순서를 설정해줘야한다. 이 때 사용할 수 있는 것이 스프링의 @Order 어노테이션이다.

    @Configuration
    @EnableWebSecurity
    @Order(1) // /admin/**
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    	...
    }
    
    @Configuration
    @Order(2) // anyRequest
    class MySecurityConfig2 extends WebSecurityConfigurerAdapter {
    	...
    }

    다음과 같이 @Order를 붙여줘서 순서를 정해주었다. 

    @Order를 붙인 순서대로 FilterChains에 SecurityFilterChain이 각각 만들어져서 순서대로 저장되는 것을 확인할 수 있다. 그럼 동작방식도 한번 보면 좋을 것 같다. 

    FilterChainProxy

    localhost:8080/admin으로 사용자 요청을 날렸다. DelegatingFilterProxy를 통해 FilterChainProxy로 사용자 요청이 넘어오는데, doFilterInternal로 넘어오게 된다. 이 때, getFilters() 메서드에 firewallRequest를 넘겨주면서 실행할 filters를 가져오는 것을 확인할 수 있다.  

    FilterChainProxy

    getFilters()를 하는 과정으로 넘어와본다. 넘어와보면 현재 가지고 FilterChainProxy가 가지고 있는 filterChains 객체를 For문을 돌리면서, chain이 request에 부합하는지를 차례대로 확인하고, 부합할 경우 그 값을 바로 넘겨주는 것을 볼 수 있다. 즉, 순서대로 처리한다는 소리다.

    Match하는 과정을 좀 더 따라들어와보면 위의 코드를 볼 수 있다. 먼저 HttpMethod가 없는 경우에는 정상적인 요청이 아니기 때문에 False를 반환하는 것을 볼 수 있다. 이후 this.pattern을 확인하는데 this.pattern은 이 FilterChain이 가지고 있는 antMatcher의 값이다. 이 값이 MATCH_ALL("**")과 같은지를 보는데, 같으면 당연히 True를 해준다. 이렇게까지 했는데도 없는 경우 좀 더 URL을 살펴본다.

    더 내려오면, URL을 모두 소문자로 바꾼 후, 값을 하나하나 비교하는 것을 확인할 수 있다. 즉, 이 과정까지 내려오는 것을 정리해보면 사용자 요청이 오고, 그 요청을 처리할 FilterChain을 선택하는 과정에서 사용자 요청 URL과 FilterChain이 가진 antMatcher가 Matching 되는지를 확인해서 인증/인가를 처리할 Filter를 선택하는 것을 볼 수 있다. 

    따라서 보안 클래스를 여러개 만든다면, 넓은 범위보다 좁은 범위의 보안 처리를 할 때의 보안 설정 정보가 @Order에 따라서 좀 더 빠르게 처리 될 수 있도록 설정해두어야 한다. 동일 보안 클래스에서 처리를 한다면, 좀 더 좁은 범위의 보안 처리를 하는 URL이 더 위에 오도록 해야한다. 

     

    정리

    • 보안 설정 클래스에 따라 securityFilterChain이 생성된다.
    • 보안 설정 클래스가 여러가지 일 경우 SecurityFilterChain이 여러 개 생성된다.
    • SecurityFilterChain은 FilterChainProxy가 내부적으로 가지는 FilterChains에 각각 저장된다. 이 변수는 리스트다.
    • FilterChainProxy의 FilterChains에 순서대로 저장되기 위해 각 보안 설정 클래스는 @Order를 통해 순번 설정이 필요하다.
    • FilterChainProxy는 인증/인가 처리를 할 SecurityFilter를 가져오기 위해 For문을 돌리면서 순서대로 요청 URL에 대응되는 antMatchers를 가지는 securityFilter를 가져온다.
    • 먼저 등록된 FilterChain이 먼저 동작하기 때문에, 좁은 범위의 보안 설정 클래스가 상대적으로 더 앞에 위치하도록 해야한다.
    • 동일 보안 설정 클래스 내에서는 좁은 범위가 좀 더 위의 antMatchers에 위치하도록 한다. 

    댓글

    Designed by JB FACTORY