CSRF 토큰을 쓰기로 했다면, 그다음 결정은 어디에 저장할 것인가다. Spring Security가 제공하는 선택지와 각각의 기술적 의미를 정리한다.
CsrfTokenRepository의 역할
CsrfTokenRepository는 Spring Security에서 CSRF 토큰의 생성, 저장, 로드를 총괄하는 핵심 인터페이스다. 어떤 구현체를 선택하느냐에 따라 CSRF 토큰의 전체 관리 전략이 결정된다.
저장 전략 비교 - Stateful vs. Stateless
HttpSessionCsrfTokenRepository
서버 중심의 Stateful 전략이다.
동작 원리
서버는 토큰을 생성하여 서버 메모리의 HttpSession 객체에 저장한다. 클라이언트에게는 토큰 자체가 아닌, 세션을 식별하기 위한 JSESSIONID 쿠키만 전달된다.
장점
- 보안성 토큰이 서버 내에만 존재하므로, 클라이언트 측의 XSS 공격 등으로 토큰이 직접 탈취될 위험이 없다.
단점
- Stateful 아키텍처 서버가 모든 사용자의 세션 정보를 메모리에 유지해야 하므로, 사용자 수에 따라 서버 리소스 사용량이 증가한다.
- 확장성 한계 여러 서버로 수평 확장(Scale-out) 시, 세션 불일치 문제가 발생할 수 있다.
- SPA 비친화적 API 통신이 잦은 SPA 환경에서는, 클라이언트가 토큰을 획득하고 관리하기가 매우 번거롭다.
CookieCsrfTokenRepository
클라이언트 중심의 Stateless 전략이다.
동작 원리
서버는 토큰을 생성하여 HTTP 응답의 Set-Cookie 헤더에 담아 클라이언트로 직접 전송한다. 클라이언트의 자바스크립트는 이 쿠키에서 토큰 값을 읽어, API 요청 시 X-XSRF-TOKEN과 같은 커스텀 HTTP 헤더에 값을 담아 전송한다.
장점
- Stateless 아키텍처 서버는 토큰 상태를 유지할 필요가 전혀 없다.
- 뛰어난 확장성 상태가 없으므로(stateless), 별도의 설정 없이도 서버 인스턴스를 자유롭게 확장할 수 있다.
- SPA 친화적 클라이언트가 API 호출 시 토큰을 명확하게 제어할 수 있으므로, SPA 프레임워크와 잘 통합된다.
단점
- 클라이언트 측 보안 토큰이 클라이언트에 저장되므로, 쿠키의 보안 속성을 올바르게 설정하지 않으면 위험에 노출될 수 있다.
현대적인 웹 애플리케이션, 특히 프론트엔드와 백엔드가 분리된 SPA 구조에서는 확장성과 유연성이 뛰어난 CookieCsrfTokenRepository (Stateless 전략)을 채택하는 것이 일반적이고 권장되는 방식이다.
SecurityConfig 구현
SecurityConfig.java에 CookieCsrfTokenRepository를 적용하는 코드는 다음과 같다.
// SecurityConfig.java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
.csrf(...) 블록 해부
CSRF 보호 기능 전체를 설정하는 블록이다.
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)
.csrfTokenRepository(...)"CSRF 토큰 저장소는 이것을 사용하겠다"고 Spring Security에 명시적으로 선언하는 부분이다.CookieCsrfTokenRepository.withHttpOnlyFalse()HttpOnly를false로 설정하여, 자바스크립트가 쿠키의 토큰 값을 읽어 헤더에 담을 수 있도록 허용한다. 'Double Submit Cookie' 패턴의 핵심 동작을 위한 필수 설정이다.
withHttpOnlyFalse 설정 관련* 설정 누락
CookieCsrfTokenRepository의 기본 생성자는 HttpOnly를 true로 설정한다. 만약 withHttpOnlyFalse()를 빼먹으면, 클라이언트의 자바스크립트가 쿠키(XSRF-TOKEN)를 읽지 못해 X-XSRF-TOKEN 헤더를 만들 수 없게 된다. 결국 서버는 헤더 값이 없다고 판단하여 모든 요청을 403 Forbidden으로 거부한다.
* Secure 속성 미고려
HTTPS를 사용하는 운영 환경에서는 쿠키가 암호화되지 않은 HTTP를 통해 전송되는 것을 막기 위해 Secure 속성을 true로 설정하는 게 좋다. CookieCsrfTokenRepository는 setCookieSecure(true) 메서드를 제공하며, 보통 프로필(profile)에 따라 이 설정을 동적으로 제어한다.
다음 글에서는 CookieCsrfTokenRepository와 함께 SPA 환경의 요청/응답 처리를 매끄럽게 만들어주는 커스텀 CsrfTokenRequestHandler 구현을 다룬다.