서블릿 컨테이너
- 톰켓과 같은 웹 애플리케이션을 서블릿 컨테이너라고 부른다.
- 서블릿 컨테이너는 Filter와 Servlet로 구성되어 있다.
- 클라이언트로부터 요청이 threadlocal로 실행되어 들어오면 각 filter들을 차례대로 거친 후 dispatchServlet이 url에따라 실행될 메소드를 찾아 Controller, Method까지 request, resonse를 넘기게된다.
- 필터는 체인처럼 엮여있기 때문에 필터 체인이라고도 불리는데, 모든 Request는 이 필터 체인을 반드시 거쳐야 서블릿 서비스에 도착할 수 있다.
SecurityFilterChain
- filter는 모든 request에 대해서 공통적으로 적용 된다. 하지만 Security는 여러가지 정책이 공존 할 수 있다.
- /api/test 하위의 요청들에 대한 보안 정책
- /admin 하위의 요청들에 대한 보안 정책
- 위의 예시처럼 경우에 따라 각각에 맞는 Security를 적용해야 하기 때문에 필터체인에 통째로 들어갈 수 없다.
- Spring에서는 이런 문제를 해결하기 위해 DelegatingFilterProxy라는 필터를 만들어 메인 필터체인에 추가하고, 해당 Proxy밑에 다시 SecurityFilterChain그룹을 등록한다.
- Proxy를 통해 filter를 선택하게 한다.
- /api/ 로 들어오는 요청은 위의 Filter를 통과하고
- / 로 들어오는 요청은 아래의 Filter를 통과하게한다.
- WebSecurityConfigurerAdapter가 필터 체인을 구성하는 Configuration Class이다.
- web resource의 경우 필터를 무시하고 통과시켜주기도 한다.
Spring Security의 Filter
- FilterChain설정은 WebSecurityConfigurerAdapter를 상속해 설정할 수 있다.
- Spring Security는 다양한 기본 Filter들을 제공한다.
- SecuritContextPersistenceFilter : SecurityContextRepository에서 SecurityContext를 로드하고 저장하는 일을 담당한다.
- HeaderWriterFilter : Http 헤더를 검사한다.
- CorsFilter : 허가된 사이트나 클라이언트의 요청인지 확인한다.
- CsrfFilter: CSRF attack을 방어하기 위한 Filter (post,put 처럼 서버의 리소스를 변경하는 요청에 대해서 내가 내려보낸 리소스에서 올라온 요청인지 체크한다.)
- LogoutFilter : 로그아웃 URL로 지정된 가상 URL에 대한 요청을 감시하고 매칭되는 요청이 있다면 사용자를 로그아웃 시킨다.
- UsernamePasswordAuthenticationFilter : username / password로 이루어진 폼기반 인증에 사용하는 가상 URL 요청을 감시하고 있으며 요청이 들어오면 사용자의 인증을 진행한다.
- ConcurrentSessionFilter : 매 요청마다 현재 사용자의 세션 만료 여부를 체크한다.
- BearerTokenAuthenticationFilter : Authorization 헤더에 Bearer 토큰이 있을 경우 인증 처리
- BasicAuthenticationFilter : HTTP 기본 인증 헤더를 감시하고 처리한다.
- RequestCacheAwareFilter : 로그인 성공 후 인증 요청에 의해 가로채어진 사용자의 원래요청을 재구성 하는데 사용된다. (로그인 페이지 이동 -> 로그인 완료 -> 목적 페이지로 이동)
- SecurityContextHolderAwareRequestFilter : Security관련 Servlet API를 지원해준다.
- RememberMeAuthenticationFilter : 세션이 사라지거나 만료가 되어도 쿠키/DB를 사용해 저장된 토큰 기반으로 인증을 처리한다.
- AnonymousAuthenticationFilter : 인증을 하지 않은 요청일 경우 SecurityContextHolder에 익명 Authentication객체를 넣어준다.
- ExcpetionTranslationFilter : 해당 Filter 이후에 인증이나 권한 예외가 발생하면 처리
- FilterSecurityInterceptor : 기본적으로 등록되는 시큐리티 필터의 마지막 필터로 이전까지의 Filter들을 통해 발행된 Authentication을 통해 접근하려하는 URL에 권한이 있는지 체크한다.
- DefaultLoginPageGeneratingFilter : 폼기반 인증에 사용하는 URL요청을 감시하고 있으며 폼 로그인에 필요한 HTML을 생성한다.
- 필터는 넣거나 뺄 수 있고 순서를 조절할 수 있다.
- 필터의 순서가 매우 critical 할 수 있기 때문에 기본 필터들은 그 순서가 어느정도 정해져 있다.
Request가 거친 Filter확인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(User.builder()
.username("user2")
.password(passwordEncoder().encode("2222"))
.roles("USER"))
.withUser(User.builder()
.username("admin")
.password(passwordEncoder().encode("3333"))
.roles("ADMIN"));
}
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) ->
requests.antMatchers("/").permitAll()
.anyRequest().authenticated());
http.formLogin();
http.httpBasic();
}
}
EnableWebSecurity에서 debug를 true로 하면 Request가 어떤 filter chain을 거쳤는지 Console에서 볼 수 있다.
filter disable
1 2 3 4 5 6 7
@Override protected void configure(HttpSecurity http) throws Exception { http.headers().disable() .csrf().disable() .logout().disable() .requestCache().disable(); }
### 여러개의 filter Chain구성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser(User.builder() .username("user2") .password(passwordEncoder().encode("2222")) .roles("USER")) .withUser(User.builder() .username("admin") .password(passwordEncoder().encode("3333")) .roles("ADMIN")); } @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/api/**/"); http.authorizeHttpRequests((requests) -> requests.antMatchers("/").permitAll() .anyRequest().authenticated()); http.formLogin(); http.httpBasic(); } }
- antMatcher를 통해 해당 filter chain이 /app/ 로 들어오는 요청에만 작동되게 할 수 있다. 만약 여러개의 Filter chain을 추가하고 싶다면 Class를 추가로 만들면 된다.
여러개의 필터체인을 구성할 때 순서가 중요하다. 그렇기 때문에 여러개의 필터 체인을 구성할 때에는 order를 설정해줘야한다.
1 2 3 4 5 6 7 8
@Order(1) @EnableWebSecurity(debug = true) @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { ..... ..... }
- antMatcher를 통해 해당 filter chain이 /app/ 로 들어오는 요청에만 작동되게 할 수 있다. 만약 여러개의 Filter chain을 추가하고 싶다면 Class를 추가로 만들면 된다.