Menu
CoddyTech

CSRF وواجهات API

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

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

يعطّل @csrf_exempt، الموجود في django.views.decorators.csrf، عملية التحقق لعرض واحد:

@csrf_exempt
def webhook(request):
    ...

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

challenge icon

التحدي

متوسط

اسمح للبرامج بإنشاء المهام من دون رمز 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))

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

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