(spring security) 필터 구현

jint·2024년 10월 27일

보안

목록 보기
7/15

이전 글에서는 인증 필터와 권한부여필터에 대해 알아봤는데 이들 모두 스프링 시큐리티에서 제공하는 특정한 http필터이다.

securityFilterChain에서 httpBasic메서드를 호출하는 것자체가 필터체인에 BasicAuthenticationFilter라는 http basic인증을 처리하는 필터를 추가하는 행위인 것이다.

필터 체인이란

처음 요청이 들어오면 필터 체인이 요청을 가로채고 필터 체인에 존재하는 여러 필터들을 정해진 순서대로 차례로 요청을 위임한다.

각 필터는 각 책임을 적용하고 다음필터에 요청을 위임할 수 있다.

필터를 만들기 위해서는 javax에서 제공하는 FIlter인터페이스를 구현하면 된다.

FIlter인터페이스는 doFilter라는 추상메서드를 가지고 있다.

BasicAuthenticationFilter이전에 요청에 필요한 헤더가 있는지 검증하는 사용자 정의 필터.

public class RequestValidationFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest,
	    ServletResponse servletResponse, 
	    FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        
        String requestId = request.getHeader("Request-Id");
        
        if(requestId == null || requestId.isBlank()) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }
        
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

javax.servlet에서 제공하는 filter인터페이스는 HttpServletRequest, HttpServletResponse가 아닌

ServletRequest, ServletResponse이기 때문에 형변환이 필요하다.

RequestValidationFilter의 책임인 Request-Id 검증이 성공적으로 완료되면 매개변수로 받은

filterChain의 doFilter메서드를 통해 다음 필터에 요청을 위임한다.

이제 이 필터를 필터체인을 BasicAuthenticationFilter이전에 추가해야한다.

이는 SecurityConfig에서 필터체인을 구성할때 addFilterBefore메서드를 통해 해결한다.

@Configuration
@EnableWebSecurity
public class ProjectConfig {
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetailsManager manager = new InMemoryUserDetailsManager();

        UserDetails user = User.withUsername("john")
                .password("12345")
                .authorities("READ")
                .build();

        manager.createUser(user);

        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http.httpBasic(Customizer.withDefaults());

        http.addFilterBefore(
                new RequestValidationFilter(),
                BasicAuthenticationFilter.class
        );
        http.authorizeHttpRequests(
              authorizeRequests -> authorizeRequests
                      .requestMatchers(HttpMethod.GET, "/a")
                      .authenticated());

        return http.build();
    }
}

기존 필터 뒤에 필터를 추가할때도 필요하다.

필터 뒤에 로깅 목적의 사용자 정의 필터를 추가하기.

public class AuthenticationLoggingFilter implements Filter {
    private final Logger logger = LoggerFactory.getLogger(AuthenticationLoggingFilter.class);
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        String requestId = request.getHeader("Request-Id");
        logger.info("Successfully authenticated user: {} ",requestId);
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http.httpBasic(Customizer.withDefaults());
        
        http.addFilterBefore(
                new RequestValidationFilter(),
                BasicAuthenticationFilter.class
        );
        http.addFilterAfter(
                new AuthenticationLoggingFilter(),
                BasicAuthenticationFilter.class
        );
        http.authorizeHttpRequests(
              authorizeRequests -> authorizeRequests
                      .requestMatchers(HttpMethod.GET, "/a")
                      .authenticated());
        
        return http.build();
    }

기존 http basic Authorization Header만 보내면
RequestValidationFilter에서 400 status인 SC_BAD_REQUEST로 response를 보낸다.

Request-Id까지 포함했다면 성공적으로 200 status를 받고 컨트롤러로 요청이 간것을 확인할 수 있다.

스프링 시큐리티가 제공하는 필터

스프링 시큐리티에는 Filter인터페이스를 구현하는 여러 추상 클래스가 있다.

스프링 시큐리티는 필터 체인에 추가한 필터를 요청당 한번만 실행하도록 보장하지는 않는데

OncePerRequestFilter라는 GenericFilterBean클래스를 확장한 클레스를 사용하면

doFilter메서드가 요청당 한번만 실행되도록 보장한다.

또한 요청 응답에 대한 http 형변환까지 해주기 때문에 따로 형변환을 할 필요가 없다

0개의 댓글