Spring Security의 필터 - CsrfFilter
2025. 8. 5. 15:22

CSRF 공격 방어

Spring Security의 CSRF 보호는 동기화 토큰 패턴(Synchronizer Token Pattern)을 사용한다.

 

CsrfFilter의 동작 원리

1. 어떤 요청에 대해 적용되나?

  • "state-changing" 요청만 검사한다.
    • 즉, POST, PUT, DELETE, PATCH 요청 등에 대해 필터가 동작.
    • GET, HEAD, TRACE, OPTIONS 요청은 검사하지 X

2. CSRF 토큰 생성 및 저장 (서버 측)

  • 사용자가 처음 애플리케이션에 접근하면,
           CsrfFilter는 내부적으로 CsrfTokenRepository를 통해 CSRF 토큰을 생성.
  • 이 토큰은 서버 세션 또는 쿠키에 저장된다.

3. 클라이언트가 요청 시 토큰을 함께 보냄 (클라이언트 측)

  • 클라이언트는 이후의 POST, PUT 등의 요청에 CSRF 토큰을 요청 헤더에 담아서 전송해야 한다.

4. 서버가 토큰을 비교하고 일치 여부 확인

  • CsrfFilter는 요청에 포함된 토큰과 CsrfTokenRepository에 저장된 토큰을 비교.
  • 두 토큰이 일치하면 요청을 허용, 다르면 403 Forbidden 반환.

5. 검사 실패 시 결과

  • 로그:
Invalid CSRF token found for request...
  • 응답:
HTTP/1.1 403 Forbidden

 

 

CsrfFilter 적용하기

가장 기본적인 SecurityFilterChain 등록

@EnableWebSecurity
@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http.build();
    }
}

 

CsrfTokenRepository 구현체를 CookieCsrfTokenRepository로 설정

CsrfTokenRequestHandler 구현체를 CsrfTokenRequestAttributeHandler로 설정

현재 내 프로젝트는 CSR(Client-Side Rendering) 방식이므로
클라이언트에서 쿠키에 저장된 CSRF 토큰에 접근해야 한다.
따라서 Http Only는 false로 설정
@EnableWebSecurity
@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
            );

        return http.build();
    }
}

동작 순서 요약

  1. 사용자가 페이지에 접근 -> 서버가 쿠키로 CSRF 토큰 전달 (XSRF-TOKEN=...)
  2. 프론트엔드는 document.cookie 등으로 토큰 꺼냄 -> 요청 시 헤더에 추가 (X-XSRF-TOKEN)
  3. 서버는 CookieCsrfTokenRepository를 통해 저장된 토큰과,
  4. CsrfTokenRequestAttributeHandler를 통해 요청에서 꺼낸 토큰을 비교
  5. 일치하면 정상 처리, 아니면 403 오류

 

각 컴포넌트 설명

1. csrfTokenRepository(...)

  • 무슨 컴포넌트?
    • CSRF 토큰을 어디에 저장하고, 어떻게 꺼낼지 정의하는 저장소.
    • CsrfTokenRepository는 인터페이스이며, 구현체를 바꿔 끼울 수 있음.
  • 기본 구현체는?
    • HttpSessionCsrfTokenRepository (기본값): 세션에 저장 / 서버 측 HttpSession
    • CookieCsrfTokenRepository: 쿠키에 저장  ☜ 현재 내가 사용한 것 / 클라이언트 측 쿠키

 

2. CookieCsrfTokenRepository.withHttpOnlyFalse()

  • 무슨 컴포넌트?
    • CSRF 토큰을 브라우저 쿠키에 저장하도록 설정함.
    • withHttpOnlyFalse()는 생성된 쿠키에 HttpOnly=false 속성을 붙여 JavaScript에서 읽을 수 있게 만듬.
  • 어떤 쿠키?
Set-Cookie: XSRF-TOKEN=abc123xyz; Path=/
  • 왜 필요?
    • 프론트엔드(JavaScript)가 이 토큰을 읽어서 헤더에 담을 수 있게 하기 위함

 

3. csrfTokenRequestHandler(...)

  • 무슨 컴포넌트?
    • 클라이언트가 보낸 요청에서 CSRF 토큰을 어디서 꺼낼 것인지 정의함.
    • CsrfTokenRequestHandler는 요청에서 토큰을 읽고 검증하도록 돕는 역할을 함.

 

4. new CsrfTokenRequestAttributeHandler()

  • 무슨 컴포넌트?
    • CsrfTokenRequestHandler의 구현체 중 하나.
    • 요청 헤더나 폼 필드에서 토큰을 꺼내서, HttpServletRequest attribute (_csrf)에 저장해줌.
  • 어떤 작업?
    • 이 설정 덕분에 컨트롤러에서 아래처럼 CSRF 토큰을 직접 꺼낼 수 있음:
@GetMapping("/form")
public String form(HttpServletRequest request) {
    CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
    return ...;
}
newJiin
newJiin