CSRF 和 API
本课属于 Coddy Django 学习旅程的“JSON API”单元。第 6 课,共 19 课。
一个真实的程序发送 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 令牌的程序创建任务。在 tasks/views.py 中,只对 task_list 豁免检查,这样 POST /api/tasks/ 就可以在没有令牌的情况下使用。
在 Shell 选项卡中运行下面这行代码。enforce_csrf_checks=True 会让测试 Client 检查令牌:它会输出 201,而不是 403。
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 在线编辑器