스프링 시큐리티 쓸 때 왜 다들 필터에 몰빵하는거야

PADO·2025년 2월 26일
post-thumbnail

서론

구글링을 해보면 Spring Security에 대한 다양한 블로그와 샘플 코드를 쉽게 찾을 수 있습니다.
하지만 많은 코드가 모든 기능을 하나의 필터에 몰아 구현한 경우가 많습니다.

최근 Spring Security in Action을 읽으며 이러한 방식이 잘못되었음을 깨달았습니다.
이번 글에서는 Spring Security의 각 클래스가 어떤 역할을 하는지 살펴보겠습니다.

들어가기 앞서

이 포스팅은 DOKBARO를 개발하면서 경험한 것을 기반으로 제작하였습니다.

DOKBARO란 ?

자기계발과 성장을 위해 독서와 스터디를 활용하는 개발자들을 위한 퀴즈 학습 플랫폼, DOKBARO입니다.

개발 서적을 즐겨 읽지만, 매번 내용을 제대로 이해했는지 확인하기 어렵지 않으셨나요? 혹은 이해 부족으로 인해 독서 스터디가 소수만 적극적으로 참여하는 형태로 변질되는 경험을 하셨을지도 모릅니다.

그래서, DOKBARO는

📚 퀴즈 출제 및 풀이 기능으로 도서 내용을 재미있고 효과적으로 이해하도록 도와드려요.

💡 스터디 리포트 기능으로 스터디원들이 책에 대해 자유롭게 의견을 나누고, 서로의 학습 현황을 확인할 수 있어요.

DOKBARO와 함께라면 도서 이해도를 높이고, 스터디 활동을 보다 풍성하고 활발하게 만들어 이상적인 독서 환경을 경험하실 수 있습니다. ✌️

현재 베타 오픈중이니 아래 링크를 통해 이용해보실 수 있어요!
https://dokbaro.com

Spring Security 구조

spring security는 다양한 컴포넌트들로 구성되어있습니다. 이 중 몇개만 어떤 역할을 하는지 한번 알아볼까요?

  • authenticationFilter: 클라이언트 요청으로부터 인증 정보를 추출하고 인증 프로세스를 시작하는 역할을 합니다.
  • authenticationManager: 인증을 처리하는 핵심 인터페이스로, 인증 요청을 받아 인증 여부를 결정합니다.
  • authenticationProvider: 실제 인증 논리를 구현하는 컴포넌트입니다. 사용자 인증을 위한 핵심 비즈니스 로직이 들어갑니다.
  • userDetailsService: 사용자의 정보를 로드하는 서비스입니다. 주로 데이터베이스에서 사용자 정보를 조회합니다.

즉, filter 내에서 userDetailsService를 직접 호출하거나, 인증 로직을 직접 처리하는 케이스는 지양해야겠죠!

Filter도 종류가 다양해

filter chain이란?

Spring Security에서 Filter Chain은 보안 관련 요청을 처리하는 일련의 필터들의 체인입니다.
클라이언트 요청이 들어오면, Spring Security는 이를 여러 개의 보안 필터를 거쳐 처리합니다.

Spring Security는 기본적으로 여러 개의 필터를 내장하고 있으며, 필요에 따라 Custom Filter를 추가하여 동작을 변경할 수 있습니다.

filter 종류

  1. 인증 관련 필터
    UsernamePasswordAuthenticationFilter → 폼 로그인 요청 처리
    BasicAuthenticationFilter → HTTP Basic 인증 처리
    BearerTokenAuthenticationFilter → JWT 등의 토큰 인증 처리
  2. 인가 관련 필터
    FilterSecurityInterceptor → 접근 제어 처리
  3. 세션 및 CSRF 관련 필터
    SessionManagementFilter → 세션 관리 및 동시 로그인 제한
    CsrfFilter → CSRF 공격 방어
  4. 기타 필터
    ExceptionTranslationFilter → 인증/인가 예외 처리
    SecurityContextPersistenceFilter → SecurityContext 유지
    필요에 따라 Custom Filter를 추가하여 특정 로직을 삽입할 수도 있습니다.

각종 handler도 설정해줘

Spring Security에서는 인증 및 인가 과정에서 발생하는 이벤트를 처리하기 위해 여러 Handler를 기본적으로 제공합니다.

  1. 인증 성공 시 (AuthenticationSuccessHandler)

    • 로그인 성공 후 특정 페이지로 리디렉션
    • 로그인 성공 로그 기록
  2. 인증 실패 시 (AuthenticationFailureHandler)

    • 로그인 실패 시 에러 메시지 반환
    • 로그인 실패 로그 기록
  3. 인가 실패 시 (AccessDeniedHandler)

    • 권한이 부족한 경우 403 Forbidden 응답 처리
    • 접근 불가 페이지로 리디렉션 가능

물론 기본적으로 정의된 헨들러 대신, 커스텀하게 선언할 수도 있습니다!
간혹가다 필터 내에서 인증 성공 여부를 판단한 이후에 바로 리다이렉트를 명령하는 분들이 있는데, 그러는 대신 커스텀한 success handler를 선언하는 것을 권장합니다!


@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        response.sendError(HttpServletResponse.SC_FORBIDDEN, "권한이 없습니다.");
    }
}

인증 실패는 AuthenticationEntryPoint를 통해서

Spring Security에서는 인증되지 않은 사용자가 보호된 리소스에 접근하려 할 때 AuthenticationEntryPoint를 사용하여 인증 실패 처리를 합니다.

AuthenticationEntryPoint의 역할

  • 인증되지 않은 사용자가 요청을 보낼 경우 401 Unauthorized 응답 반환
  • JSON 형식으로 커스텀 에러 메시지를 반환할 수도 있음
  • 로그인 페이지로 리디렉션하는 등의 추가 처리 가능

간혹 AccessDeniedHandler랑 혼동하시는 분들이 있는데, AccessDeniedHandler 와 AuthenticationEntryPoint의 책임은 서로 다릅니다!
인증되지 않은 사용자가 요청을 보낼 때 혹은 인증을 실패할 때는 AuthenticationEntryPoint 를 통해 401 RESPONSE를 반환하고,
인증은 되었지만, 권한이 부족한 사용자가 접근할 때는 AccessDeniedHandler를 통해 403 RESPONSE를 반환하면 됩니다!

etc

1. filter bean 으로 선언하는 것 지양

Spring에서 Filter를 Bean으로 등록하는 것은 지양해야 합니다.
Filter를 Bean으로 선언하면 Spring의 내장 컨테이너에 자동 등록됩니다.
그런데 Servlet 단에서도 동일한 Filter가 한 번 더 등록될 수 있어, 결국 Filter가 두 번 실행되는 문제가 발생할 수 있습니다.
이를 방지하려면 Spring Security의 필터 체인에서 명시적으로 필터를 등록하고, 직접 Bean으로 등록하는 방식은 피하는 것이 좋습니다.
https://docs.spring.io/spring-boot/reference/web/servlet.html#web.servlet.embedded-container.servlets-filters-listeners

2. Authentication 객체에 대한 가변성

특히 Authentication 구현체 중 하나인 UsernamePasswordAuthenticationToken 를 사용시에는 authorities가 있으면 인증된 것으로 인지하니 사용 시 유의해야 합니다.

결론

Spring Security는 결국 Spring을 기반으로 동작하며, 이를 효과적으로 사용하려면 각 객체를 적절한 책임과 역할에 맞게 구성해야 합니다.

특히, 인증 및 권한 처리를 할 때도 Filter를 Bean으로 등록하지 않는 것, AuthenticationEntryPoint와 AccessDeniedHandler의 역할을 구분하는 것처럼 프레임워크의 구조를 이해하고 활용하는 것이 중요합니다.

Spring Security는 복잡하지만, 프레임워크가 제공하는 방식에 맞춰 사용하면 유지보수성과 확장성이 높아집니다.

프레임워크를 이용하기로 결정했다면 최대한 프레임워크의 의도된 용도에 맞게 이용한다.
《스프링 시큐리티 인 액션》 (로렌티우 스필카 지음, 최민석 옮김) 중에서

Spring Security를 사용할 때도 이 원칙을 기억하며, 프레임워크의 의도를 존중하는 방향으로 개발하는 것이 가장 효과적인 접근법이라고 생각합니다.

DOKBARO에서는 spring security에 관련한 퀴즈도 제공하니, 관심 있으신 분들은 한번 풀어보시는 것을 추천드릴게요!
https://dokbaro.com/book/4367?page=1

이상으로 포스팅 마치겠습니다. 감사합니다!

profile
뒤로 밀리는 듯해도 끊임 없이 나아간다. 마치 파도처럼

0개의 댓글