| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- hexagonal
- Ubiquitous Language
- 안드로이드 스튜디오
- spring boot
- JPA
- BOJ
- CSRF
- 헥사고날
- nopasswd
- rabbitmq
- DTO Projection
- clean architecture
- Rabbit MQ
- Context map
- Hexagonal Architecture
- event
- Domain-Driven
- 이벤트 기반
- 헥사고날 아키텍처
- Domain Driven
- Spring
- 공통 언어
- android studio
- 클린 아키텍처
- Thymeleaf
- 컨텍스트 맵
- EC2
- 백준
- subdomain
- Spring Cloud
Archives
- Today
- Total
취미겸생업
[Spring boot] CSRF 토큰과 Session 생성 타이밍 불일치 본문
SMALL
문제 상황
로컬 개발 환경에선 문제 없던 Thymeleaf Spring MVC에서 비로그인 시 Post Detail 로딩이 불가한 상황이 발생했다.
- 클라이언트 에러
Failed to load resource: net::ERR_INCOMPLETE_CHUNKED_ENCODING
- 애플리케이션 에러
s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/posts/2] as the response has already been committed. As a result, the response may have the wrong status code.
- 추측
- 개발 환경과 배포 환경의 큰 차이는, 배포 환경은 Docker 상에서 NGINX와 같은 Reverse Proxy를 통해 요청이 들어오며 로컬 환경에 비해 CPU, Memory가 심각하게 모자란다는 점.
전제
- Spring Security의 기본 설정은 POST 요청 시 CSRF 토큰을 검사해 CSRF 공격을 방지한다.
- 이 과정에서 Thymeleaf HTML 내에 포함된 GET을 제외한 모든 메서드의 Form에 Spring Security가 CSRF 토큰을 hidden으로 자동으로 포함해준다. (th:action 속성 사용 시)
- 배포 환경의 Reverse Proxy(Nginx) 아키텍처는 데이터 전송의 효율성을 위해 백엔드의 응답을 즉시 스트리밍(Chunked)한다
문제점
- Spring Security 6+부터는 CSRF 토큰이 필요할 때만 생성되어 사용되는 지연로딩 방식이다
- 배포 환경의 Reverse Proxy(Nginx) 아키텍처는 데이터 전송의 효율성을 위해 백엔드의 응답을 즉시 스트리밍(Chunked)한다
- th:action 속성을 사용하는 Form 태그를 렌더링 하는 시점에 지연 로딩된 CSRF 토큰을 생성하기 위해 세션 쿠키 헤더(Set-Cookie)를 추가하려다 프로토콜 위반으로 예외가 발생한다
- 이미 HTML 본문 일부가 렌더링이 진행되었기 때문에 에러 페이지로도 Forwarding이 불가능해 ERR_INCOMPLETE_CHUNKED_ENCODING 에러가 브라우저에 발생한다
요약 : Spring Security 6+의 CSRF 토큰 지연로딩과 NGINX Streaming(Chunked) 방식의 시너지
- 로컬 순수 톰캣에선 8KB 가량의 버퍼 사이즈가 찰 때 까지 Flush가 발생하지 않지만 배포 환경의 경우 네트워크 지연이나 NGINX의 즉시 스트리밍 방식에 의해 버퍼가 Flush 되는 시점이 빠르기 때문에 환경에 따라 동작이 달라진다
해결법
요청마다 명시적으로 세션 생성하기
@GetMapping("/posts/{id}") public String postDetail( ... HttpServletRequest request ) { request.getSession(); // 세션 조회, 세션이 존재하지 않을 경우 생성 ... }- 장점 : 코드 한 줄로 해결되고, 컨트롤러마다 명시적으로 세션 생성 시점을 정의할 수 있다
- 단점 : 보일러 플레이트
Spring Security의 HttpSession 정책을 Always로 설정
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http ... .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) ); return http.build(); }- 장점 : 모든 요청에 대해 사전에 강제적으로 세션을 생성해서 보일러 플레이트를 방지할 수 있다
- 단점 : 세션이 필요없는 요청도 세션을 생성하기 때문에 메모리 점유가 높아져 오버헤드가 생길 수 있고, 동시 접속자가 많을 경우 네트워크 부하가 증가한다.
Thymeleaf Chunk 단위 렌더링 비활성화
spring: thymeleaf: servlet: produce-partial-output-while-processing: false- 장점 : 부분 렌더링을 원천 차단하는 설정 하나 만으로 문제 해결이 가능하다
- 단점 : 사용자가 처음 화면을 보기까지 시간이 걸려 UI/UX 문제가 있으며, 다수의 사용자가 동시 접속 시 힙(Heap) 메모리에 부하가 있을 수 있다
Cookie 기반 CSRF 설정
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) ); return http.build(); }- 장점 : 쿠키 기반의 CSRF를 사용해서 Session 방식의 메모리 오버헤드에서 자유롭다, Stateless 환경에 잘 어울리기에 비교적 현대적이다
- 단점 : HttpOnly Cookie가 아니기 때문에 XSS 공격에 의한 CSRF 토큰 탈취에 취약해진다. 이스케이핑 문자에 대한 방어 전략이 필요하다.
결론
포트폴리오를 On-Premise 환경에서 계속 돌려야 하는 입장에서 성능과 안정성 모두 쉽게 포기할 수 없었다.
모든 텍스트 입력에 대한 XSS CSRF 탈취 공격에 대한 방어 전략을 적용하기도 부담스러웠다.
따라서 문제 발생 시 직접 Session 생성 시점을 정의하기로 결정했다..
완벽한 정답을 찾지 못했다.
LIST
'IT > 트러블슈팅' 카테고리의 다른 글
| [Spring boot] 낙관락, 비관락 의사결정 기준에 대해 (0) | 2026.07.08 |
|---|---|
| [Spring boot] Soft-Delete와 Unique 제약조건 고찰 (0) | 2026.07.07 |
| [Spring boot] 사용자의 리소스 접근 권한에 대해 (0) | 2025.04.10 |
| [Eclipse] 이클립스 Github 연동 (0) | 2024.11.22 |
| [Android Studio/Java] Switch 문 R.id (1) | 2023.11.08 |
