Django登录接口遇CSRF验证失败403错误,求原因及解决方法
Hey there, let's break down why you're hitting this CSRF error and fix it step by step. Your code is on the right track, but there are a few key adjustments and checks needed to resolve the issue.
Why This Error Happens
- Decorator Precedence Issue: Even though you added
@csrf_exempt, Django REST Framework's@api_viewwraps your function into a class-based view under the hood. If the decorators aren't ordered correctly, thecsrf_exemptrule might not apply properly to the final view. - Session Authentication Conflict: By default, DRF includes
SessionAuthenticationin its global auth settings. This means even if you're using Token Authentication, DRF will still enforce CSRF checks for requests that involve session cookies. - Incorrect Testing Method: If you're testing the login with a standard browser form submission (instead of an API client like Postman or curl), browsers automatically handle CSRF cookies—but your view is designed for API requests that don't rely on session-based CSRF validation.
Fixes to Try
1. Adjust Decorator Order (Ensure @csrf_exempt Takes Effect)
For DRF function views, @csrf_exempt needs to be applied outside the @api_view decorator to ensure it wraps the view that DRF creates. Here's the corrected order:
from django.shortcuts import render from rest_framework.status import ( HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND, HTTP_200_OK ) from rest_framework.decorators import api_view from rest_framework.response import Response from django.contrib.auth import authenticate from django.views.decorators.csrf import csrf_exempt from rest_framework.authtoken.models import Token from rest_framework.decorators import permission_classes from rest_framework.permissions import AllowAny @csrf_exempt @api_view(["POST"]) @permission_classes((AllowAny,)) def login(request): username = request.data.get("username") password = request.data.get("password") if username is None or password is None: return Response({'error': 'Please provide both username and password'}, status=HTTP_400_BAD_REQUEST) user = authenticate(username=username, password=password) if not user: return Response({'error': 'Invalid Credentials'}, status=HTTP_404_NOT_FOUND) token, _ = Token.objects.get_or_create(user=user) return Response({'token': token.key}, status=HTTP_200_OK)
2. Remove Session Authentication from Global Settings (Token-Only APIs)
If your project only uses Token Authentication for APIs, edit your settings.py to remove SessionAuthentication from the default auth classes. This stops DRF from enforcing CSRF checks for token-based requests:
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', # Remove this line: 'rest_framework.authentication.SessionAuthentication' ], # ... other DRF settings ... }
3. Switch to Class-Based APIView (Cleaner Control)
Using DRF's APIView class gives you clearer control over CSRF and permissions. Here's how to rewrite your login view with explicit CSRF exemption:
from django.shortcuts import render from rest_framework.status import ( HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND, HTTP_200_OK ) from rest_framework.views import APIView from rest_framework.response import Response from django.contrib.auth import authenticate from rest_framework.authtoken.models import Token from rest_framework.permissions import AllowAny from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator @method_decorator(csrf_exempt, name='dispatch') class LoginView(APIView): permission_classes = [AllowAny] def post(self, request): username = request.data.get("username") password = request.data.get("password") if username is None or password is None: return Response({'error': 'Please provide both username and password'}, status=HTTP_400_BAD_REQUEST) user = authenticate(username=username, password=password) if not user: return Response({'error': 'Invalid Credentials'}, status=HTTP_404_NOT_FOUND) token, _ = Token.objects.get_or_create(user=user) return Response({'token': token.key}, status=HTTP_200_OK)
Then update your urls.py to use this class view:
from django.urls import path from .views import LoginView urlpatterns = [ path('login/', LoginView.as_view(), name='login'), # ... other URLs ... ]
4. Test with an API Client
If you were testing with a browser form, switch to tools like Postman, curl, or Thunder Client. These tools don't send session cookies by default, so CSRF checks won't trigger for token-based requests. Example curl command:
curl -X POST http://your-domain/login/ -d "username=your-username&password=your-password"
内容的提问来源于stack exchange,提问作者lohith devapatla

