Menu
CoddyTech

CSRF وواجهات API

جزء من قسم واجهات API بصيغة JSON في رحلة Django على Coddy. الدرس 6 من 19.

يتلقى برنامج حقيقي يرسل POST /api/tasks/ الاستجابة 403 Forbidden. تريد حماية CSRF في Django أن يتضمن كل طلب POST رمزًا سريًا من إحدى صفحاتك، حتى لا يتمكن موقع آخر من جعل متصفح الزائر يرسل نماذجك. لا يملك البرنامج صفحة كهذه ولا رمزًا.

يؤدي @csrf_exempt، من django.views.decorators.csrf، إلى تعطيل التحقق لعرض واحد:

@csrf_exempt
def webhook(request):
    ...

لكن لذلك ثمنًا: يمكن لأي موقع الآن جعل متصفح الزائر يرسل طلب POST هذا. لا بأس بذلك في عرض لا يعتمد على هوية المستخدم الذي سجل دخوله، مثل قائمة المهام. أما واجهة API التي تعمل بصفة المستخدم المسجل دخوله، فتبقي حماية CSRF مفعّلة، ويرسل البرنامج النصي لصفحتها الرمز في ترويسة X-CSRFToken.

challenge icon

التحدي

متوسط

اسمح للبرامج التي لا تحتوي على رمز 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))

جميع دروس واجهات API بصيغة JSON

تدرّب بنفسك: Playground لـ Django