본문 바로가기
Spring

[Spring] SpringSecurity - Filter Chain

by worldcenter 2024. 11. 10.

SpringSecurity Filter Chain

  • 스프링 시큐리티는 애플리케이션의 인증, 인가 등의 보안 기능을 제공하는 스프링 하위 프로젝트 중 하나로 보안과 관련된 많은 기능을 제공하고 있습니다.
  • Spring Security도 인증 및 인가를 처리하기 위해 Filter를 사용하는데 FilterChainProxy를 통해서 상세 로직을 구현하고 있습니다.

 

 

SpringSecurity 동작 구조

스프링 시큐리티는 서블릿 필터(Servlet Filter)를 기반으로 동작하며, DispatcherServlet 앞에 필터가 배치되어 있습니다.

 

위 그림의 필터 체인(FilterChain)은 서블릿 컨테이너에서 관리하는 ApplicationFilterChain을 의미합니다. 스프링 시큐리티는 사용하고자 하는 필터체인을 서블릿 컨테이너의 필터 사이에서 동작시키기 위해 DelegatingFilterProxy를 사용합니다.

 

 

DelegatingFilterProxy는 서블릿 컨테이너의 생명주기와 스프링 애플리케이션 컨텍스트(ApplicationContext) 사이에서 다리 역할을 수행하는 필터 구현체 입니다. 표준 서블릿 필터를 구현하고 있으며, 역할을 위임할 필터체인 프록시(FilterChainProxy)를 내부에 가지고 있습니다. 필터체인 프록시는 스프링 부트의 자동 설정에 의해 자동 생성됩니다.

 

FilterChainProxy는 스프링 시큐리티에서 제공하는 필터로서 SecurityFilterChain을 통해 많은 SecurityFilter를 사용할 수 있습니다.

@Configuration
@EnableWebSecurity // Spring Security 지원을 가능하게 함
@RequiredArgsConstructor
public class WebSecurityConfig {

    private final JwtUtil jwtUtil;
    private final UserDetailsServiceImpl userDetailsService;
    private final AuthenticationConfiguration authenticationConfiguration;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
        JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil);
        filter.setAuthenticationManager(authenticationManager(authenticationConfiguration));
        return filter;
    }

    @Bean
    public JwtAuthorizationFilter jwtAuthorizationFilter() {
        return new JwtAuthorizationFilter(jwtUtil, userDetailsService);
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf((csrf) -> csrf.disable());

        // 기본 설정인 Session 방식은 사용하지 않고 JWT 방식을 사용하기 위한 설정
        http.sessionManagement((sessionManagement) ->
                sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );

        http.authorizeHttpRequests((authorizeHttpRequests) ->
                authorizeHttpRequests
                        .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정
                        .requestMatchers("/").permitAll() // 메인 페이지 요청 허가
                        .requestMatchers("/api/user/**").permitAll() // '/api/user/'로 시작하는 요청 모두 접근 허가
                        .anyRequest().authenticated() // 그 외 모든 요청 인증처리
        );

        http.formLogin((formLogin) ->
                formLogin
                        .loginPage("/api/user/login-page").permitAll()
        );

        // 필터 관리
        http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

 

FilterChainProxy에서 사용할 수 있는 SecurityFilterChain은 List 형식으로 담을 수 있게 설정돼 있어 URI 패턴에 따라 특정 보안필터 체인을 선택해서 사용하게 됩니다.

 

SecurityFilterChain에서 사용하는 필터는 여러 종류가 있으며, 각 필터마다 실행되는 순서가 다릅니다. 공식 문서에서 소개하는 필터의 실행 순서는 다음 링크에서 확인 가능합니다.

 

 

UsernamePasswrodAuthenticationFilter를 통한 인증 과정

 

1. 클라이언트로부터 요청을 받으면 서블릿 필터에서 SecurityFilterChain으로 작업이 위임되고 그중 UsernamePasswordAuthenticationFilter(여기서는 AuthenticationFilter) 에서 인증을 처리합니다.

2. UsernamePasswordAuthenticationFilter는 요청 객체(HttpServlet)에서 username과 password를 추출해서 토큰을 생성합니다.

3. 그러고 나서 AuthenticationManager에게 토큰을 전달합니다. AuthenticationManager는 인터페이스이며, 일반적으로 사용되는 구현체는 ProviderManager 입니다.

4. ProviderManager는 인증을 위해 AuthenticationProvider로 토큰을 전달합니다.

5. AuthenticationProvider는 토큰의 정보를 UserDetailsService에 전달합니다.

6. UserDetailsService는 전달 받은 정보를 통해 데이터베이스에서 일치하는 사용자를 찾아 UserDetails 객체를 생성합니다.

7. 생성된 UserDetails 객체는 AuthenticationProvider로 전달 됩니다.

8. 해당 Provider에서 인증을 수행하고 성공하게 되면 ProviderManager로 권한을 담은 토큰을 전달합니다.

9. ProviderManager는 검증된 토큰을 AuthenticationFilter로 전달합니다.

10. AuthenticationFilter는 검증된 토큰을 SecurityContextHoler에 있는 SecurityContext에 저장합니다.

11. 실패하면 SecurityContextHolder를 비웁니다.