Django는 CsrfViewMiddleware로 CSRF(Cross-Site Request Forgery)를 막는다. 기본 아이디어는 Double Submit Cookie, 즉 쿠키에 든 CSRF 값과 요청에 함께 보낸 값을 비교하는 것이고, 여기에 토큰 마스킹(token masking)과 Origin·Referer 검사를 더한다.
흐름
- 페이지를 렌더링할 때
csrftoken쿠키를 발급한다 - 템플릿의
{% csrf_token %}이 폼에 숨은 필드csrfmiddlewaretoken을 넣는다 - POST 등 안전하지 않은 메서드 요청이 오면, 미들웨어가 쿠키 값과 폼 필드(또는 헤더) 값을 비교한다
- 맞지 않으면 403
왜 안전한가
공격 사이트는 피해자 브라우저로 요청을 보낼 수는 있고, 브라우저는 csrftoken 쿠키를 자동으로 붙인다. 하지만 동일 출처 정책(Same-Origin Policy) 때문에 다른 출처의 쿠키 값을 읽을 수는 없어서, 요청 본문이나 헤더에 같은 값을 넣지 못한다. 쿠키가 HttpOnly가 아니어도 다른 출처의 스크립트는 읽지 못한다. 단, 내 사이트에 XSS(Cross-Site Scripting)가 있으면 읽힌다.
SPA에서 쓰기
SPA(Single-Page Application)인 React·Next.js처럼 폼을 템플릿으로 렌더링하지 않는 클라이언트는 쿠키 값을 읽어 헤더로 보낸다.
const token = document.cookie.match(/csrftoken=([^;]+)/)?.[1]
await fetch('/api/orders/', {
method: 'POST',
credentials: 'include',
headers: { 'X-CSRFToken': token, 'Content-Type': 'application/json' },
body: JSON.stringify(order),
})- 이 방식이려면
CSRF_COOKIE_HTTPONLY = False(기본값)여야 자바스크립트가 쿠키를 읽는다 - 프론트와 API의 출처가 다르면
CSRF_TRUSTED_ORIGINS에 프론트 출처를 넣는다(CORS)
쿠키가 아예 없을 때: ensure_csrf_cookie
Django는 템플릿에서 {% csrf_token %}을 쓰거나 코드가 get_token()을 부를 때만 csrftoken 쿠키를 내려준다. 그래서 페이지를 Django 템플릿이 아니라 정적 HTML(SSG·CDN)이나 별도 프론트 서버가 그리면, 처음 온 방문자는 쿠키 없이 첫 POST를 보내고 403을 받는다. 프론트가 빌드 때나 서버에서 API를 불렀더라도 그 응답의 Set-Cookie는 사용자 브라우저에 닿지 않는다. 페이지 렌더 방식을 바꿔도 이 문제는 그대로이고, 오히려 정적 페이지는 하이드레이션 직후 요청이 몰려 순서가 더 꼬이기 쉽다.
- 브라우저가 처음 부르는 GET 뷰(예: 현재 사용자 정보, 또는 전용
/csrf/엔드포인트)에@ensure_csrf_cookie를 붙여 쿠키를 강제로 내려준다 - 클라이언트는 첫 POST 전에 토큰을 확보한다. 앱 시작 시 그 GET을 먼저 기다리거나, POST 직전에 쿠키가 없으면 받아 오게 한다. 동시에 출발한 GET과 POST 사이에는 순서 보장이 없다
- 익명 트래킹 엔드포인트처럼 지킬 상태가 없는 곳만
@csrf_exempt를 검토한다. 로그인·결제처럼 상태를 바꾸는 엔드포인트에는 쓰지 않는다 - DRF(Django REST Framework)의
SessionAuthentication은 로그인한 요청에만 CSRF를 검사한다. 익명이어야 할 요청이 CSRF 실패로 403을 받는다면 DRF가 아닌 일반 Django 뷰이거나, 세션 쿠키가 남아 있어 인증된 요청으로 처리된 경우다
순수 Double Submit보다 나아간 점
- 마스킹: 폼·헤더로 나가는 토큰은 요청마다 무작위 마스크를 씌운 값이라 쿠키 값과 겉보기에 다르다. 서버가 마스크를 벗겨 비교한다. 압축 응답의 길이 차이로 비밀을 추측하는 BREACH(Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext) 공격을 어렵게 하려는 장치다
- 출처 검사: Origin 헤더가 있으면 허용된 출처인지 확인하고, HTTPS 요청에서 Origin이 없으면 Referer를 엄격히 검사한다(Origin·Referer 헤더와 Referrer-Policy)
로그인 흐름에서 미들웨어가 어떤 순서로 도는지는 Django 미들웨어와 로그인 흐름, 쿠키 속성(SameSite·Secure)은 HTTP 쿠키를 본다.
출처: Django 문서: CSRF How it works · AJAX에서 CSRF 보호 쓰기 · CSRF 설정 · AJAX 페이지에서 HTML 폼 없이 쓰기(ensure_csrf_cookie) · DRF — SessionAuthentication