본문 바로가기
Back-end

Spring Security Filter

by 신재권 2024. 1. 1.

작성 이유

회사 프로젝트에는 Auth 모듈이 개별적으로 존재한다.

해당 모듈은 주로 Auth 기능을 담당하는데, JWT 토큰 관련 설정도 존재한다.

앱과 웹 환경이 존재하는데, 둘의 JWT Provider가 다르다.

 

웹 쪽에서 버그가 발견되었는데, 한쪽 로그인을 해놓고, 다시 로그인을 하면 헤더에 토큰이 존재하는 상태로 요청이 간다.

즉, 중복 로그인 처리 문제인데, 시나리오 상 토큰을 실은 상태로 로그인을 시도하면, 로그인 된 것으로 간주해야 한다.

하지만 토큰 시그니처가 맞지 않다는 에러가 발생 → 클라이언트에서도 해당 버그를 인지하지 못한 상태여서 에러 처리가 안되어 있어 다시 로그인 시도

위 과정을 반복하며 무한 로그인 시도가 되는 버그가 있었다.

 

이로 인해 Auth 모듈에 요청이 비정상적으로 많이 발생했다.(4일간 로그 포함 70만건 이상)

이를 발견하고, 시큐리티 설정을 까보며 확인해봤는데, 디버깅 하며 돌려보니 웹 환경인데도 앱 환경 Jwt Filter를 타는 문제가 있었다.

즉, 로그인 요청에 JWT 토큰 발급해주는 곳은 웹 환경 JWT Provider 인데, 앱 환경 JwtFilter도 검사해버리니 토큰 시그니처가 다르다는 에러를 발생 시킨 것이다.

 

테스트 코드를 보니, 테스트는 작성되어 있으나, 해당 케이스는 고려되어 있지 않았다.

이를 위해 시큐리티 설정 디버깅을 하며 테스트 코드를 돌려봤는데, 앱 환경 Config가 되어 있지 않음에도 불구하고 웹 url 요청이 타는 문제를 발견했다.

 

해결 방법은 간단하게 앱 환경쪽에 웹 url 을 제외시키면 된다.

하지만 근본적인 이유를 찾고 싶어 필터 쪽을 다시 복습겸 공부하게 되었다.

→ 1/1 아직 이유는 못찾았다, 왜 웹에서 요청한 url이 해당 필터를 타는지 아직 모르겠다. 지속적으로 디버깅 중이다.

→ 1/2 permitAll()은 모든 요청을 허용한다. 하지만 필터는 탄다. 즉 토큰을 실어서 보내면 문제가 발생하게 된다. 이를 해결하기 위해서는 기존 해결 방법 처럼 url을 제외시켜야 한다.

 

Filter 란?

스프링 시큐리티에서 HTTP 필터는 HTTP 요청에 적용되는 다양한 책임을 위임한다.

스프링 시큐리티의 HTTP 필터는 일반적으로 요청에 적용해야 하는 각 책임을 관리하며 책임의 체인을 형성한다.

필터는 요청을 수신하고 그 논리를 실행하며 최종적으로 체인의 다음 필터에 요청을 위임한다.

 

Spring Security Architecture Filter Implements

스프링 시큐리티 아키텍처의 필터는 일반적인 HTTP 필터이다.

필터를 만들려면 javax.servlet 패키지의 Filter 인터페이스를 구현한다.

  • ServletRequest : HTTP 요청을 나타낸다. 해당 객체를 이용해 요청에 대한 세부 정보를 얻는다.
  • ServletResponse : HTTP 응답을 나타낸다. 해당 객체를 이용해 응답을 클라이언트로 다시 보내기 전에 또는 더 나아가 필터 체인에서 응답을 변경한다.
  • FilterChain : 필터 체인을 나타낸다. FilterChain 객체는 체인의 다음 필터로 요청을 전달한다.

필터 체인은 필터가 작동하는 순서가 정의된 필터의 모음을 나타낸다.

스프링 시큐리티에는 몇 가지 필터 구현과 순서가 있다.

  • BasicAuthenticationFilter : HTTP Basic 인증을 처리한다.
  • CsrfFilter : CSRF(사이트 간 요청 위조)를 처리한다.
  • CorsFilter : CORS(교차 출처 리소스 공유) 권한 부여 규칙을 처리한다.

새 필터는 필터 체인의 다른 필터를 기준으로 추가된다.

즉, 기존 필터 위치 또는 앞이나 뒤에 새 필터를 추가할 수 있다.

각 위치는 인덱스이며 순서라고도 할 수 있다.

  • 여러 필터가 같은 위치에 있으면 필터가 호출되는 순서는 정해지지 않는다.

 

필터 추가

필터를 추가하는 방법은 다음과 같다.

  1. 필터를 구현한다 : 요청에 필요한 것을 확인하는 클래스를 만든다.
  2. 필터 체인을 필터에 추가한다 : 구성 클래스에서 필터 체인에 필터를 추가한다.

특정 위치에 필터를 추가해도 스프링 시큐리티는 해당 위치에 필터가 하나라고 가정하지 않는다.

필터 체인의 같은 위치에 필터를 더 추가할 수 있으며, 해당 경우 스프링 시큐리티는 필터가 실행되는 순서를 보장하지 않는다.

 

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

스프링 시큐리티에는 Filter 인터페이스를 구현하는 여러 추상 클래스가 있으며, 이를 위해 필터 정의를 확장할 수 있다.

또한 이러한 클래스는 구현을 확장할 때 이점을 얻을 수 있는 기능을 추가한다.

GenericFilterBean은 Filter를 확장하며, 스프링의 부가적인 클래스들을 확장한다.

즉, Filter의 역할을 하며, 스프링 구성 요소를 사용할 수 있다.

OncePerRequestFilter는 GenericFilterBean을 확장하는 더 유용한 클래스이다.

프레임워크는 필터 체인에 추가한 필터를 요청당 한 번만 실행하도록 보장하지 않는다.

OncePerRequestFilter는 필터의 doFilter() 메서드가 요청당 한 번만 실행되도록 논리를 구현한다.

즉, GenericFilterBean을 직접 확장해서 사용하는 것 보다, 같은 요청이 여러 번 처리되지 않게 해주는 OncePerRequestFilter를 확장해서 사용하는 것이 좋다.

 

OncePerRequestFilter 특징

  • HTTP 요청만 지원하지만 사실은 이것만 이용한다 : 해당 클래스의 장점은 형식을 형 변환하며 HttpServletRequest 및 HttpServletResponse로 직접 요청을 수신한다는 것이다. Filter 인터페이스의 경우에는 요청과 응답을 형 변환해야 한다.
  • 필터가 적용될지 결정하는 논리를 구현할 수 있다 : 필터 체인에 추가한 필터가 특정 요청에는 적용되지 않는다고 결정할 수 있다. 이 경우 shouldNotFilter(HttpServletRequest) 메서드를 재정의 하면 된다. 기본적으로 필터는 모든 요청에 적용된다.
  • OncePerRequestFilter는 기본적으로 비동기 요청이나 오류 발송 요청에는 적용되지 않는다 : 이 동작을 변경하려면 shouldNotFilterAsyncDispatch() 및 shouldNotFilterErrorDispatch() 메서드를 재정의하면 된다.

 

Filter vs GenericFilterBean

 

Filter 코드

package jakarta.servlet;

import java.io.IOException;

public interface Filter {

    public default void init(FilterConfig filterConfig) 
									throws ServletException {}
    public default void destroy() {}
}

 

GenericFilterBean

package org.springframework.web.filter;

public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware,
		EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {

protected final Log logger = LogFactory.getLog(getClass());

	@Nullable
	private String beanName;

	@Nullable
	private Environment environment;

	@Nullable
	private ServletContext servletContext;

	@Nullable
	private FilterConfig filterConfig;

	private final Set<String> requiredProperties = new HashSet<>(4);
	
	....
}
  • GenericFilterBean 클래스는 Filter 를 구현했기 때문에, Filter의 역할 + 스프링 설정 정보 사용 이 가능하다.
  • Filter와 GenericFilterBean은 매 서블릿마다 호출된다. → 다른 서블릿으로 dispatch 되면 필터 로직을 한 번 더 탄다.(ex- forward)
  • OncePerRequestFilter는 서블릿에서 다른 서블릿으로 dispatch 되어도 다시 해당 필터가 실행되지 않는다. 즉 사용자의 요청 당 딱 한 번만 실행되는 필터를 만들 수 있다. → 불필요한 중복 인증 처리를 하지 않는다.

 

요약

  • 웹 애플리케이션의 첫 번째 계층은 HTTP 요청을 가로채는 필터 체인이다. 스프링 시큐리티 아키텍처의 다른 구성 요소는 요구 사항에 맞게 맞춤 구성이 가능하다.
  • 필터 체인에서 기존 필터 위치 또는 앞이나 뒤에 새 필터를 추가해 필터 체인을 맞춤 구성할 수 있다.
  • 기존 필터와 같은 위치에 여러 필터를 추가할 수 있으며 이 경우 필터가 실행되는 순서는 정해지지 않는다.
  • 필터 체인을 변경하면 애플리케이션의 요구 사항에 맞게 인증과 권한 부여를 맞춤 구성하는 데 도움이 된다.

'Back-end' 카테고리의 다른 글

Spring Validation in Kotlin  (0) 2024.01.14
Spring @Async + ThreadPoolTaskExecutor  (1) 2024.01.14
코틀린 - 기본 문법  (1) 2023.10.25
JPA Entity 기본 생성자  (0) 2023.09.29
기본 키 할당 전략  (0) 2023.09.29