CSRF와 API
Coddy Django 여정의 JSON API 섹션에 포함된 레슨. 19개 중 6번째.
POST /api/tasks/를 보내는 실제 프로그램은 403 Forbidden 응답을 받습니다. Django의 CSRF 보호 기능은 다른 웹사이트가 방문자의 브라우저를 조종해 양식을 제출하지 못하도록 모든 POST 요청에 우리 웹사이트 페이지에서 발급한 비밀 토큰이 포함되기를 요구합니다. 프로그램에는 그런 페이지도 토큰도 없습니다.
django.views.decorators.csrf의 @csrf_exempt는 특정 뷰에서 검사를 비활성화합니다.
@csrf_exempt
def webhook(request):
...이렇게 하면 보안상의 대가가 따릅니다. 이제 어떤 웹사이트든 방문자의 브라우저가 이 POST 요청을 보내도록 할 수 있습니다. 로그인한 사용자가 누구인지에 따라 동작이 달라지지 않는 뷰, 예를 들어 작업 목록 뷰에는 괜찮습니다. 로그인한 사용자로 동작하는 API는 CSRF 보호를 계속 활성화하며, 해당 페이지의 스크립트는 X-CSRFToken 헤더에 토큰을 담아 보냅니다.
챌린지
중급CSRF 토큰 없이도 프로그램이 작업을 생성할 수 있도록 합니다:
- 토큰이 없어도
POST /api/tasks/가 작동합니다. task_list만 예외로 처리됩니다.
실행을 누른 다음 셸 탭에 다음을 입력하세요. 변경 전에는 403이 출력되고, 변경 후에는 201이 출력됩니다: from django.test import Client; print(Client(enforce_csrf_checks=True).post("/api/tasks/", {"title": "Pay rent"}, content_type="application/json").status_code)
직접 해보기
import json
from django.http import JsonResponse
from django.shortcuts import render
from .models import Task
def task_to_dict(task):
return {
"id": task.id,
"title": task.title,
"done": task.done,
"priority": task.priority,
}
def home(request):
return render(request, "tasks/home.html", {"tasks": Task.objects.all()})
def stats(request):
data = {
"total": Task.objects.count(),
"done": Task.objects.filter(done=True).count(),
}
return JsonResponse(data)
def task_list(request):
if request.method == "POST":
data = json.loads(request.body)
task = Task.objects.create(
title=data["title"],
priority=data.get("priority", "normal"),
)
return JsonResponse(task_to_dict(task), status=201)
tasks = Task.objects.all()
done = request.GET.get("done")
if done == "true":
tasks = tasks.filter(done=True)
elif done == "false":
tasks = tasks.filter(done=False)
return JsonResponse([task_to_dict(task) for task in tasks], safe=False)
def task_detail(request, pk):
task = Task.objects.filter(pk=pk).first()
if task is None:
return JsonResponse({"error": "Task not found"}, status=404)
return JsonResponse(task_to_dict(task))
JSON API의 모든 레슨
직접 연습해 보세요: Django 플레이그라운드