Spring-Security
CSRF 토큰 유지 방법, HttpOnly 설정, 처리 필터 및 CsrfTokenRequestHandler
neal89
2025. 5. 29. 13:45
1. CSRF 토큰 유지 방법 (서버 측)
CSRF 토큰은 서버가 발급하고, 클라이언트에게 전달되어 다시 서버로 돌아올 때 유효성을 검증하는 방식입니다. 서버는 발급한 토큰을 클라이언트로부터 받은 토큰과 비교하기 위해 저장하고 있어야 합니다. 이 저장 방식은 주로 세션 또는 쿠키를 통해 이루어집니다.
- 세션을 이용한 CSRF 토큰 유지 (HttpSessionCsrfTokenRepository):
- 설명: Spring Security의 기본 CSRF 토큰 저장 방식입니다. 서버는 CSRF 토큰을 생성한 후, 사용자 세션(HttpSession)에 해당 토큰을 저장합니다. 클라이언트에게는 이 토큰 값을 HTML 폼의 숨겨진 필드나 HTTP 헤더를 통해 전달합니다. 클라이언트로부터 요청이 다시 들어오면, CsrfFilter는 요청에 포함된 토큰과 현재 세션에 저장된 토큰을 비교하여 유효성을 검증합니다.
- 장점: 토큰이 서버 메모리(세션)에 저장되므로 클라이언트가 토큰을 저장할 필요가 없고, 토큰 탈취 시에도 세션이 만료되면 함께 무효화됩니다.
- 단점: 서버 세션에 의존하므로, 분산 환경(여러 대의 서버)에서는 세션 클러스터링이나 세션 고정(Sticky Session) 설정이 필요할 수 있습니다. 각 요청마다 세션을 조회해야 하므로 오버헤드가 발생할 수 있습니다.
- 쿠키를 이용한 CSRF 토큰 유지 (CookieCsrfTokenRepository):
- 설명: 서버는 CSRF 토큰을 생성한 후, 이 토큰을 쿠키에 담아 클라이언트에게 전송합니다. 동시에, 이 토큰 값을 HTML 폼의 숨겨진 필드나 HTTP 헤더를 통해 클라이언트에게도 전달합니다. 클라이언트로부터 요청이 들어오면, CsrfFilter는 요청에 포함된 토큰과 클라이언트가 보낸 쿠키에 담긴 토큰을 비교하여 유효성을 검증합니다.
- 장점: 서버가 세션을 유지할 필요가 없어 Stateless한 환경(특히 REST API)에 적합합니다. 서버 부하를 줄일 수 있습니다.
- 단점: 토큰이 클라이언트 쿠키에 저장되므로 XSS(Cross-Site Scripting) 공격에 취약해질 수 있습니다. (그러나 HttpOnly 플래그로 보호 가능)
- Spring Security에서의 설정 예시:
Java
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) // HttpOnly 비활성화 (JS에서 읽을 수 있도록) // 또는 HttpOnly true (JS에서 못 읽게, JWT와 같이 사용할 때) // .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyTrue())) .authorizeHttpRequests(...) .build(); }
2. JavaScript에서 토큰을 읽을 수 있도록 HttpOnly 설정
HttpOnly 플래그는 쿠키에만 적용되는 설정입니다. HttpOnly 플래그가 설정된 쿠키는 웹 브라우저의 JavaScript에서 접근할 수 없습니다. 이는 XSS(Cross-Site Scripting) 공격으로부터 쿠키 정보(특히 세션 ID)를 보호하는 데 매우 효과적입니다.
- 세션: 서버 세션에 저장된 토큰은 HttpOnly 설정과 직접적인 관련이 없습니다. 세션 정보는 서버 측 메모리나 DB에 저장되는 것이지, 클라이언트 측 쿠키에 저장되는 것이 아니기 때문입니다. 다만, 세션 ID를 담은 쿠키에는 HttpOnly를 설정하여 세션 탈취를 막는 것이 일반적입니다.
- 쿠키 (CookieCsrfTokenRepository 사용 시):
- CookieCsrfTokenRepository를 사용하여 CSRF 토큰을 쿠키에 저장하는 경우, 이 쿠키에 HttpOnly 플래그를 설정할 수 있습니다.
- 만약 React와 같은 프론트엔드에서 JavaScript를 사용하여 AJAX 요청 시 CSRF 토큰을 HTTP 헤더에 포함시켜야 한다면, 해당 CSRF 토큰 쿠키는 HttpOnly를 false로 설정해야 합니다. (예: CookieCsrfTokenRepository.withHttpOnlyFalse()) 이렇게 해야 JavaScript가 쿠키에서 토큰 값을 읽어올 수 있습니다.
- 반대로 HttpOnly를 true로 설정하면 JavaScript가 쿠키를 읽을 수 없으므로, CSRF 토큰을 HTML의 숨겨진 필드나 메타 태그 등 다른 방식으로 클라이언트에게 전달하고 JavaScript는 그 값을 읽어 사용해야 합니다.
3. CSRF 토큰을 처리하는 필터 및 CsrfTokenRequestHandler
- CSRF 토큰을 처리하는 필터: Spring Security에서 CSRF 토큰의 유효성을 검사하고, 필요한 경우 토큰을 생성하여 HttpServletRequest 속성으로 추가하는 핵심 필터는 바로 CsrfFilter 입니다. CsrfFilter는 Spring Security의 필터 체인에서 AuthorizationFilter 또는 FilterSecurityInterceptor보다 앞에 위치하여 요청이 보안 검사를 받기 전에 CSRF 토큰을 먼저 검증합니다.
- CsrfTokenRequestHandler가 어디에서 실행돼? CsrfTokenRequestHandler는 CsrfFilter 내부에서 사용되는 인터페이스입니다. CsrfFilter는 이 인터페이스의 구현체를 사용하여 다음 두 가지 주요 작업을 수행합니다.
- 토큰 로드: 현재 요청에 대한 CsrfToken을 로드합니다. (세션이나 쿠키 등에서)
- 요청 매칭: 수신된 요청의 CSRF 토큰을 로드된 토큰과 비교하여 유효성을 검증하고, 요청이 유효한지 판단합니다. 즉, CsrfTokenRequestHandler의 메서드들은 CsrfFilter에 의해 호출되어 CSRF 토큰의 로드 및 검증 로직을 수행합니다.
- XorCsrfTokenRequestHandler가 기본값인가? Spring Security 5.x 버전까지는 XorCsrfTokenRequestHandler가 기본값이었지만, Spring Security 6.0부터는 XorCsrfTokenRequestAttributeHandler가 기본값으로 사용됩니다. XorCsrfTokenRequestAttributeHandler는 CsrfFilter가 요청에 CSRF 토큰을 HttpServletRequest의 속성으로 추가하는 방식을 담당합니다. 클라이언트(HTML, JavaScript)가 이 속성에서 토큰을 읽어 요청에 다시 포함시킬 수 있도록 합니다. 이는 Double Submit Cookie 패턴과 Synchronizer Token 패턴을 혼합한 방식으로 작동하여 보안성을 높입니다.
4. CSRF 토큰의 인코딩/디코딩
CSRF 토큰 자체는 인코딩/디코딩 과정을 거치지 않습니다.
- CSRF 토큰은 서버에서 생성될 때 이미 안전한 문자열(예: Base64 인코딩된 랜덤 바이트 배열) 형태로 생성됩니다.
- 클라이언트에게 전달되기 전에 디코딩 되는 것이 아니라, 그대로(인코딩된 형태 그대로) 전달됩니다. HTML 폼의 _csrf hidden 필드나 X-CSRF-TOKEN 헤더에 삽입될 때, 또는 쿠키에 저장될 때 별도의 디코딩 과정은 없습니다.
- 전달받은 토큰 값을 비교하기 직전에 디코딩하는 것이 아니라, 서버가 저장하고 있는 토큰 값과 클라이언트로부터 받은 토큰 값을 동일한 인코딩/형태로 직접 비교합니다. 예를 들어, 서버가 Base64 인코딩된 토큰을 생성했다면, 세션에 Base64 형태로 저장하고 클라이언트에게도 Base64 형태로 전달하며, 클라이언트가 다시 보낸 토큰도 Base64 형태로 받아와 Base64 형태로 직접 비교합니다. 별도의 디코딩 과정은 유효성 검증 전에 수행되지 않습니다. 이는 토큰 값의 일치 여부만 확인하면 되기 때문입니다.
정리:
- CSRF 토큰은 서버 세션 또는 쿠키에 유지됩니다. 세션 기반은 서버 부하, 쿠키 기반은 XSS 취약성(HttpOnly로 방어)을 고려해야 합니다.
- HttpOnly는 쿠키에만 적용되며, JavaScript에서 CSRF 토큰을 읽어서 헤더에 포함하려면 CSRF 토큰을 담는 쿠키의 HttpOnly를 false로 설정해야 합니다.
- CsrfFilter는 CSRF 토큰을 처리하는 핵심 필터이며, CsrfTokenRequestHandler는 CsrfFilter 내에서 토큰 로드 및 요청 매칭 로직을 수행하는 인터페이스입니다.
- Spring Security 6.0부터 XorCsrfTokenRequestAttributeHandler가 CSRF 토큰 처리를 위한 기본 핸들러입니다.
- CSRF 토큰은 클라이언트에게 전달되거나 서버에서 비교될 때 별도의 디코딩 과정을 거치지 않습니다. 토큰 생성 시 이미 안전한 형태로 생성되며, 그 형태 그대로 유효성 비교가 이루어집니다.