2년전 나를 엉엉 울게 만들던 그 CSRF 맞고요
여기서 진짜 원인을 모르겟다며 해결하지 못했다로 끝났는데요
거 뭐냐 s3+cloudfront로 배포하고 공부하면서 갑자기 뇌를 스쳐지나가길래 급히 써봅니다
1. 발생 현상
프론트엔드(localhost)에서 백엔드(Django)로 POST 요청을 보낼 때마다 403 Forbidden (CSRF 검증 실패) 에러가 발생함. Axios의 xsrfCookieName 설정을 공식 문서대로 맞췄음에도 해결되지 않음
2.근본 원인: 도메인 불일치
프론트엔드(로컬)와 백엔드(API 서버)의 주소가 다르기 때문에 백엔드가 프론트엔드로 내려준 CSRF 토큰 쿠키가 브라우저 입장에서는 서드파티(제3자) 쿠키로 취급됨
3. 브라우저의 보안 정책 충돌 (블로그의 SameSite 경고)
모던 브라우저들은 서드파티 쿠키를 차단하기 때문에, 이를 허용하려면 쿠키에 SameSite=None과 Secure(HTTPS) 속성이 강제됨. 하지만 로컬 개발 환경은 HTTP였고, 자바스크립트에서 쿠키를 읽어 헤더에 담으려 했으나 보안 속성(HttpOnly 등)과 브라우저 정책이 얽히면서 프론트엔드 단에서 토큰에 접근조차 할 수 없는 상태가 된것
배포환경에선 잘됐었다~ 대충 2년전에 이랬고요
아니 근데 이번에는 안그랬거덩요
근데 분명 csrf 토큰이 쿠키에 저장되는걸 봤단말이지요
해결방법은 여러가지였음
1. 단일 도메인으로 통합한다
app.com/ -> 프론트
app.com/api/ -> 백엔드
2. 서브도메인 통일 + 쿠키 설정
# Django 예시
SESSION_COOKIE_DOMAIN = ".app.com"
CSRF_COOKIE_DOMAIN = ".app.com"
CSRF_COOKIE_SAMESITE = "Lax"
3. JWT + Authorization Header (쿠키 안 씀)
// 쿠키 대신 헤더로 인증
headers: { "Authorization": `Bearer ${token}` }
4. CORS + SameSite=None 설정
// 백엔드에서
CORS_ALLOW_CREDENTIALS = True
CSRF_COOKIE_SAMESITE = "None"
CSRF_COOKIE_SECURE = True # HTTPS 필수
// 프론트에서
fetch(url, { credentials: "include" })
SameSite=None은 반드시 secure(HTTPS)랑 같이 써야함
근데 저는 CloudFront로 도메인을 통일해버린것입니다.
그 과정이 어떻게 돌아가느냐묜~...
1. 브라우저 눈에는 korip.me 하나밖에 안보임.
사용자가 인터넷 주소창에 https://korip.me를 치고 들어옴 -> 이때 브라우저는 s3나 장고의 존재는 전혀 모름
브라우저가 대화하는 유일한 상대는 CloudFront(korip.me)임
- 사용자가 마이페이지 버튼을 누름
- 프론트 코드가 /api/mypage로 요청을 보냄
- 브라우저는 현재 주소가 korip.me니까 당연히 https://korip.me/api/mypage로 요청을 날림
- 브라우저: 난 korip.me한테 요청했음. 도메인 똑같으니까 CORS 통과. 쿠키도 퍼스트 파티네? 통과ㄱㄱ(브라우저는 api.korip.me의 존재를 모름)
2.CouldFront의 가로채기와 분류 (라우팅)
- 브라우저가 쏜 korip.me/api/mypage 요청은 CloudFront 대문으로 들어옴
- 여기서 내가 설정해둔 /api/* 동작(Behavior) 규칙이 발동함
- CloudFront: 얼래? 주소에 /api/가 붙어있네? 이거는 S3로 가면 안됨 ㄴㄴㄴ 내가 대신 본 서버(api.korip.me)가서 물어보고 오겠음
3. 대리인 역할
- CloudFront가 브라우저 몰래 뒤에서 백엔드 진짜 주소인 https://api.korip.me/api/mypage로 요청을 전달해줌
- 백엔드는 오 누가(CloudFront) 요청했네?? 데이터 주겠음 ㅇㅇ 하고 응답함
4. 브라우저에게 전달
- 백엔드한테 데이터를 받아온 CloudFront는 다시 브라우저에게 데이터를 건네줌
- 브라우저: korip.me가 데이터 잘 갖다줬넹~
이런것이다!
요약하자면
CloudFront에 원본(Origin)으로 api.korip.me를 등록하고 동작(Behavior)으로 /api/*를 연결한것은
사용자(브라우저)야 너는 korip.me 하나만 보고 통신해라!(도메인 통합)
나머지는 뒤에서 api.korip.me라는 진짜 백엔드 주소로 데이터를 배달하는 귀찮은건 CloudFront인 내가 몰래 해주겟음.
이정도....?
이런걸 리퍼스 프록시라고 한다고 ... 배웠다..............
아무튼 도메인 통일하면서 2년 전의 나를 엉엉 울리던 CSRF가 왜 됐다 안됐다 했는지 이제야 이해했다.
'기타' 카테고리의 다른 글
| 라이트하우스 성능 (0) | 2026.04.21 |
|---|---|
| aws 프론트 배포 405 에러 (1) | 2025.09.22 |
| 블로그를 방치했다. (1) | 2025.06.03 |
| 왜 Vite 쓰셨어요? (0) | 2024.12.16 |
| Github Action을 이용한 AWS S3로 자동 배포하기 (1) | 2024.10.07 |