이 시리즈에 나오는 모든 내용은 인프런 인터넷 강의 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 에서 기반된 것입니다. 그리고 여기서 인용되는 PPT 이미지 또한 모두 해당 강의에서 가져왔음을 알립니다.
여태 하나의 설정 클래스만 사용했는데, 만약 2가지 이상의 설정 클래스를 쓰면
어떻게 해야할까? 그리고 이때 스프링 시큐리티는 내부적으로 어떤 아키텍처로 동작하게 될까?
앞에서는 FilterChainProxy, 즉 springSecurityFilterChain 빈 객체 내에서
보안 필터 list를 갖고, 그 list를 순회하면서 보안처리를 한다고 했다.
하지만 더 자세히 들여다 보면, 이 보안 필터 리스트들을 하나의 단위로 또 묶어서 관리하는데,
이 단위를 SecurityFilterChain
이라고 한다.
그리고 이 SecurityFilterChain
은 스프링 시큐리티 설정 클래스 하나당 하나씩 만들어진다.
그리고 이렇게 생성된 SecurityFilterChain 들은 또 하나의 list 형태로
FilterChainProxy 내부에서 SecurityFilterChains
라는 이름으로 저장 및 사용된다.
이렇게 하면 좋은 점이, 각 설정 클래스에서 지정되는 RequestMatcher
설정에 의해서
어떤 필터 묶음(SecurityFilterChain)이 실행되야 될지 FilterChainProxy 내에서 결정할 수 있다.
예를 들어 SecurityConfig1
에 http.antMatcher(“/admin/**)
를 지정하고
SecurityConfig2
에 http.antMatcher(“/user/**)
라고 지정했다고 치자.
이 상태에서 /admin/~~
이라는 요청이 FilterChainProxy
에 오면
FilterChainProxy
은 각 필터 묶음들이 갖는 RequestMatcher 를 확인해본다.
이때 SecurityConfig1
에서 생성된 보안 필터 묶음(SecurityFilterChain)의 RequestMatcher 가 현재 요청을 처리해준다는 것을 알아내고, 해당 필터 묶음으로 요청을 위임하게 되는 것이다.
간단한 테스트를 위해서 다중 시큐리티 설정 클래스를 설정해보자.
import org.springframework.core.annotation.Order;
// ... import 일부 생략 ...
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@Order(0)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**") // 내부적으로 AntPathRequestMatcher 가 생성된다.
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
@Configuration(proxyBeanMethods = false)
@Order(1)
class SecurityConfig2 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 딱히 설정을 안해서 AnyRequestMatcher 가 생성된다.
.authorizeRequests()
.anyRequest().permitAll()
.and()
.formLogin();
}
}
설정을 했으니, 서버를 실행해보고 어떻게 FilterChainProxy 내부에서
어떻게 초기화가 되는지 자세히 보자.
이 메소드 내부에서 각 설정 클래스에 따른 필터 목록들이 securityFilterChain 이라는
변수명으로 사용되는 것을 확인할 수 있다.
1번 과정:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().mvcMatchers("/favicon.ico");
web.ignoring().mvcMatchers("/error");
}
설정 클래스에 위와 같이 ignoring
코드가 존재하면,
하나의 ignore
당 하나의 SecurityFilterChain
이 생긴다.
2번 과정:
이 과정이 실제 설정 클래스를 통해서 생성된 필터 목록을 들고 있는
SecurityFilterChain
를 생성한다.
3번 과정
1번, 2번 과정을 통해서 생성된 SecurityFilterChain
들은 모두 securityFilterChains
라는 List 변수에 저장했는데, 그 List 를 new FilterChainProxy()의 인자로 줌으로써
이후에 생성된 FilterChainProxy 내부에서 접근하여 사용하게 된다.
참고 이미지) 디버깅 포인트 잡은 후 관찰한 모습
이제 직접 요청을 날려서 FilterChainProxy 내부에서 어떻게 분기처리 하는지 알아보자.
일단 사용자 요청이 오면 무조건 FilterChainProxy 가 해당 요청을 가로챈다.
여기서 볼 것은 getFilter 메소드이다. 이 메소드를 통해서 현재 FilterChainProxy 가
갖고 있는 SpringFilterChains
들 중에서 어떤 SpringFilterChain
을 사용할지 결정한다.
2번에서 찾은 SpringFilterChain 이 들고 있는 필터들을 사용해서 요청을 처리한다.
위 그림을 보면 알겠지만 우리가 이전에 설정한 Security2 클래스의 내용대로
필터 목록에 로그인 관련 필터(UsernamePAsswordAuthenticationFilter
)가 있는 것을
확인할 수 있다.