티스토리 뷰

Spring Security를 적용하기 위해서는 @Configuration 어노테이션이 달린 설정 클래스를 구현해야 하고,

이 클래스에서 SecurityFilterChain을 생성하여 보안 필터 체인을 구성합니다.


이때 보안 필터 체인에서 사용할 Filter 클래스 중 하나로 OncePerRequestFilter 클래스를 선택하여 구현할 수 있습니다.

 

 

OncePerRequestFilter 클래스를 본격적으로 살펴보기에 앞서서,

Spring Security에서 Filter를 구성하기 위해 앞서 정의해야할 것들이 있어 이해를 위해 순서대로 차례로 정리해보겠습니다.

 

 

@Configuration : 시큐리티 구성을 위한 클래스

기존 방식으로 @configuration 어노테이션이 달린 설정 클래스는

아래와 같이 WebSecurityConfigurerAdapter 상속받고, configure() 메소드를 오버라이드 해서 구현했지만

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .and()
            .logout()
                .logoutUrl("/logout")
                .permitAll();
    }
 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("{noop}password").roles("USER")
                .and()
                .withUser("admin").password("{noop}password").roles("ADMIN");
    }
}

 

 

Deprecated.
Use a SecurityFilterChain Bean to configure HttpSecurity or a WebSecurityCustomizer Bean to configure WebSecurity

 

WebSecurityConfigurerAdapter가 Deprecated 되어서,

Spring Security 5.3부터 새롭게 도입된 방식인 SecurityFilterChain 을 @Bean으로 등록해서 필터 체인을 구성할 수 있습니다.

 

따라서 이 글에서는 SecurityFilterChain 을 생성해서 구현해 보도록 하겠습니다.

 

 

SecurityFilterChain

SecurityFilterChain는 WebSecurity 클래스에서 제공하는 build() 메서드를 사용하여 보안 필터 체인을 구성합니다.


Spring Security 구성에서 SecurityFilterChain를 사용하면, 보안 필터 체인의 구성과 보안 설정을 분리하여 각각 독립적으로 구성할 수 있습니다.

 

아래 코드는 SecurityFilterChain 로 필터 체인을 구성한 예시인데,

 

이번 글에서 중점적으로 다루고 있는 OncePerRequestFilter 에 대한 설정은

new JwtConfigurer(jwtTokenProvider) 부분에서 Filter를 처리하는 메소드를 분리하여 구현했습니다.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    private JwtTokenProvider jwtTokenProvider;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .cors().configurationSource(corsConfigurationSource()).and()
                .authorizeRequests()
                .antMatchers("/api/v1/auth/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .httpBasic().disable()
                .formLogin().disable()
                .apply(new JwtConfigurer(jwtTokenProvider)); // filter 처리하는 부분

        http.exceptionHandling()
                .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                .accessDeniedHandler(new CustomAccessDeniedHandler());

        return http.build();
    }

SecurityFilterChain 에서 다른 설정도 필수로 필요한 사항이 있지만,

이 글은 Filter 처리에 관한 내용을 다루기 때문에 생략하겠습니다.

 

 

JwtConfigurer : JwtAuthenticationFilter - addFilterBefore() 적용하는 사용자 클래스

JwtConfigurer 클래스는 SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> 를 상속받고,

configure() 메서드를 오버라이드 해서 구현합니다.

 


SecurityConfigurerAdapter 클래스를 상속받을 때는 제네릭 타입으로 두 개의 클래스를 지정해야 합니다. 

  • 첫 번째 DefaultSecurityFilterChain : 보안 필터 체인의 타입
  • 두 번째 HttpSecurity : Spring Security에서 사용하는 HTTP 보안 구성을 위한 객체 HttpSecurity
public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private JwtTokenProvider jwtTokenProvider;

    public JwtConfigurer(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    public void configure(HttpSecurity http) {
        JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(jwtTokenProvider);
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

 

http.addFilterBefore()

  • 첫 번째 매개변수 : 등록할 필터 객체
    • JwtAuthenticationFilter 인스턴스를 넣었습니다.
    • JWT 토큰을 검증하고 인증된 사용자의 정보를 SecurityContext에 설정하는 필터입니다. ⭐
  • 두 번째 매개변수 : 필터가 적용될 위치를 지정한 Class 객체
    • 필터의 실행 순서를 정하는 데 사용되는 클래스를 전달합니다.
    • 해당 클래스는 인스턴스가 아니라 클래스 자체여야 합니다.

 

따라서 아래와 같이 사용하면

http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

UsernamePasswordAuthenticationFilter 필터 직전에

우리가 정의해놓은 JwtAuthenticationFilter 가 걸리도록 할 수 있습니다.

 

 

필터 순서를 정해서 사용하는 이유

로그인 폼을 사용하지 않고 API를 통해 직접 인증을 처리하는 경우에는, UsernamePasswordAuthenticationFilter와 같은 인증 필터를 사용하지 않습니다.
대신, 직접 만든 JWT 인증 필터와 같은 필터를 사용자가 직접 정의해서 JWT 토큰을 검사하고, 인증 처리합니다.

이 경우에도 보안 필터 체인에서 필터들의 순서는 중요합니다.

순서를 지정하고 인증 필터에서 인증 실패를 반환하면, 로그인 폼 데이터를 사용하여 인증을 시도하는 UsernamePasswordAuthenticationFilter를 건너뛰고, 401 Unauthorized 에러를 반환받을 수 있기 때문에 필터 순서를 지정하여 사용합니다..

 

 

JwtAuthenticationFilter : OncePerRequestFilter를 상속받는 사용자 지정 클래스

드디어 OncePerRequestFilter 까지 왔습니다!

 

이 필터 클래스는 javax.servlet.Filter 인터페이스를 구현하고 있으며, 각 HTTP 요청에 대해 한 번만 필터링을 수행하도록 보장하는 필터입니다.

OncePerRequestFilter 를 상속 받으면 doFilterInternal() 메서드를 오버라이드 하여 구현해야 합니다.

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
	    // 다음 필터로 체인 처리
	    filterChain.doFilter(request, response);
	}

}

1. extends OncePerRequestFilter 를 통해 OncePerRequestFilter 클래스를 상속 받습니다.

2. doFilterInternal() 메서드를 오버라이드 합니다.

3. 직접 만든 필터 클래스에 @Component 어노테이션을 추가해서 Spring Bean으로 등록합니다.

 

 

OncePerRequestFilter 사용하는 이유?

OncePerRequestFilter 클래스를 사용하여 필터를 구현하면, 각 HTTP 요청에 대해 한 번만 필터링 하는 것을 보장합니다.
addFilterBefore() 메서드를 사용하면 필터의 우선순위를 설정할 수 있기 때문에 여러개의 필터를 적용해서 순서를 적용할 수 있습니다.

 

 

 

OncePerRequestFilter 구현 완성 코드

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private JwtTokenProvider jwtTokenProvider;

    public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String token = jwtTokenProvider.resolveToken(request); // 1.
            if (token != null && jwtTokenProvider.validateToken(token)) { // 2.
                Authentication auth = jwtTokenProvider.getAuthentication(token); // 3.
                SecurityContextHolder.getContext().setAuthentication(auth); // 4.
            }
        } catch (Exception e) {
            request.setAttribute("businessException", e);
        }
        filterChain.doFilter(request, response); // 5.
    }

}

1. jwtTokenProvider.resolveToken(request)를 호출하여 HTTP 요청 헤더에서 JWT 토큰을 가져옵니다.

 

2. 토큰이 존재하고, jwtTokenProvider.validateToken(token)이 참이어서 토큰이 유효한 경우인증 객체를 가져오도록 조건을 걸어줍니다.

 

3. jwtTokenProvider.getAuthentication(token)을 호출하여 Authentication 인증 객체 를 가져옵니다.

여기서 인증 객체는 사용자의 인증 정보를 포함하고 있습니다.

 

4. SecurityContextHolder.getContext().setAuthentication(auth) 를 호출하여 Security Context 에 인증 객체를 설정해서 요청을 시도한 사용자가 인증 완료됩니다.

 

5. filterChain.doFilter(request, response);

다음 필터를 호출하기 위해 사용합니다. 보안 필터 체인에서 각 필터는 다음 필터를 호출하고, 호출된 다음 필터는 다시 그 다음 필터를 호출하는 방식으로 HTTP 요청에 대한 처리를 진행합니다.
여기서 다음 필터는 이전에 설정해놓은 UsernamePasswordAuthenticationFilter 인증 필터가 될 것입니다.

 

 

그래서 결론적으로 Spring Security 에서 Filter 를 쓰는 이유는?

보안 관련 로직을 필터 단위로 구현하면 관리하기 쉬워서 사용합니다.

 

  • Filter는 HTTP 요청에 대한 처리를 담당하며, Spring Security에서 보안 필터 체인을 구성하는 각각의 필터는 HTTP 요청에 대한 처리를 수행하기 위해 Filter를 사용합니다.
  • Filter를 통해 HTTP 요청에 대한 처리를 단계적으로 수행하므로, 보안 처리 로직을 각 단계별로 쉽게 파악이 가능합니다.
  • Filter는 Java Servlet API의 표준 인터페이스이므로, Spring Security와 함께 사용하기 쉽습니다.

 

 

 

 

spring security 에서 JWT 를 사용하니 더욱 Filter 에 대한 개념이 모호했는데 정리하고 보니 단순히 Filter 를 단편적으로 따로 생각하는게 아니라 전체적으로 봐야했다.
아직 시큐리티 이해도 40% 정도 까지 온거 같다..(보면 볼수록 더 어렵다)

 

 

 

 

 

 

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함