Django项目Token认证失败:登出接口测试异常求助
我在开发Django项目的用户逻辑时,发现基于Session Cookie的登出功能在浏览器中正常运行,但测试时无法工作。登录响应中的Cookie似乎没被后续请求正确保存,测试返回“Authentication credentials were not provided”错误。
项目相关代码
views.py
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.contrib.auth.base_user import AbstractBaseUser from django.db.models.manager import BaseManager from rest_framework import generics, status, permissions, serializers from rest_framework.views import APIView from rest_framework.request import Request from rest_framework.response import Response from rest_framework.authtoken.models import Token # from rest_framework.authentication import SessionAuthentication # from rest_framework.exceptions import PermissionDenied from apps.users.models import User from apps.users.serializers import ( UserSerializer, UserLoginSerializer, ) class UserLoginView(generics.GenericAPIView): serializer_class: type = UserLoginSerializer def get_object(self) -> AbstractBaseUser: """Overwrite method to get the authenticated user object""" try: session_key: str = self.request.COOKIES.get("session") user: AbstractBaseUser = Token.objects.get(key=session_key).user except ObjectDoesNotExist: user = None return user def post(self, request: Request) -> Response: response: Response user: AbstractBaseUser try: # parse data data: dict = { k: v[0] if type(v) is list and len(v) == 1 else v for k, v in dict(request.data).items() } # Verify the absence of session token serializer: serializers.Serializer = self.get_serializer(data=data) user = self.get_object() if user: raise ValidationError("Already logged in.") # Run serializer validations user = serializer.is_valid() if not user: raise ValidationError("Unable to log in with provided credentials.") # Create response content (in this case, just a confirmation message) response_content: dict[str, str] = { "detail": "Login successful", } # Create response object response = Response(data=response_content, status=status.HTTP_200_OK) # Aggregate cookie to the response token: Token token, _ = Token.objects.get_or_create(user=serializer.validated_data) response.set_cookie( key="session", value=token.key, secure=True, httponly=True, samesite="lax", ) except ValidationError as error: response = Response( {"detail": error.message}, status=status.HTTP_401_UNAUTHORIZED, ) return response class UserLogoutView(generics.GenericAPIView): def get_serializer_class(self) -> serializers.Serializer: return serializers.Serializer # Return basic Serializer class def delete(self, request: Request) -> Response: response: Response try: session_key: str = request.COOKIES.get("session") token: Token = Token.objects.get(key=session_key) response = Response( status=status.HTTP_200_OK, data={"detail": "Logout successful"}, ) response.delete_cookie(key="session") token.delete() except ObjectDoesNotExist: response = Response( {"error": "Session cookie not found"}, status=status.HTTP_401_UNAUTHORIZED, ) except Exception as error: print(error) response = Response( {"detail": str(error)}, status=status.HTTP_400_BAD_REQUEST, ) return response
tests_views.py
class TestLogoutViews(TestCase): def setUp(self) -> None: self.client = Client() self.data: dict[str, str] = { "username": "testuser", "email": "test@test.com", "password": "Password1", } self.logout_url: str = reverse("user_logout") self.login_url: str = reverse("user_login") self.client.post( reverse("user_register"), json.dumps(self.data), content_type="application/json", ) self.data.pop("email") def test_logout_view_correct_DELETE(self) -> None: # Log in first response: HttpResponse = self.client.post( self.login_url, json.dumps(self.data), content_type="application/json", ) print() print(response.status_code, self.client.cookies) print(self.client.get("").wsgi_request.user) print() # Send the DELETE request response: HttpResponse = self.client.delete(self.logout_url) # Check if the response is 200 self.assertEqual( response.status_code, 200, msg=( f"Response is {response.status_code}, expected 200. ", f"Response content: {response.content}", ), ) # Check if the cookie is deleted self.assertFalse( "session" in self.client.cookies, msg="Cookie should be deleted" )
settings.py
REST_FRAMEWORK: dict[str, str] = { "DEFAULT_AUTHENTICATION_CLASSES": ( "rest_framework.authentication.TokenAuthentication", "rest_framework.authentication.SessionAuthentication", ), "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", }
测试输出
200 Set-Cookie: session=5875a98d252d48a019226a342fb09cb785271a32; HttpOnly; Path=/; SameSite=lax; Secure AnonymousUser ====================================================================== FAIL: test_logout_view_correct_DELETE (users.test_views.TestLogoutViews) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\user\repositories\flicks-and-picks\src\backend\apps\users\test_views.py", line 518, in test_logout_view_correct_DELETE self.assertEqual( AssertionError: 401 != 200 : ('Response is 401, expected 200. ', 'Response content: b\'{"detail":"Authentication credentials were not provided."}\''))
更新:测试请求头的尝试
通过独立测试程序发现添加包含Authorization字段的请求头可避免“Authentication credentials were not provided”提示,但仍不清楚该字段的正确值设置。测试代码及输出如下:
测试代码
import os import json import requests client = requests.session() login_url = "http://127.0.0.1:8000/users/login/" logout_url = "http://127.0.0.1:8000/users/logout/" login_data: dict[str, str] = {"username": "test1", "password": "Password1"} print() response: requests.Response = client.post(login_url, json=login_data) print(response.status_code, response.json()["detail"]) print() print(client.cookies) # Save the session cookie cookies = response.cookies session_cookie = response.cookies['session'] headers = {'Authorization': f'Token {session_cookie}'} print() response: requests.Response = client.delete(logout_url, cookies=cookies) print(response.status_code, response.json()["detail"]) print() response: requests.Response = client.delete(logout_url, headers=headers, cookies=cookies) print(response.status_code, response.json()["detail"])
输出
200 Login successful <RequestsCookieJar[<Cookie session=734df1e09726dd888a90ef25fc5b6f15151d8125 for 127.0.0.1/>]> 401 Authentication credentials were not provided. 401 Token matching query does not exist.
问题原因及解决方案
核心问题:
settings.py中配置了DRF的TokenAuthentication和SessionAuthentication作为默认认证类,登出视图未跳过认证检查,DRF会优先尝试用默认认证方式验证,但你的自定义逻辑是从Cookie中取Token,DRF默认认证不识别这个自定义Cookie,因此抛出认证错误。具体修复步骤:
- 给
UserLogoutView添加权限配置,允许未认证访问(自行处理Cookie Token验证):class UserLogoutView(generics.GenericAPIView): permission_classes = [permissions.AllowAny] # 添加此行 # 原有代码保持不变 - 修复独立测试中的Authorization头:
session_cookie是Cookie对象,需取其value属性,正确写法:headers = {'Authorization': f'Token {session_cookie.value}'} - 可选优化:自定义认证类让DRF识别Cookie中的Token,替代默认
TokenAuthentication:
然后更新# apps/users/authentication.py from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from rest_framework.authtoken.models import Token class CookieTokenAuthentication(BaseAuthentication): def authenticate(self, request): session_key = request.COOKIES.get('session') if not session_key: return None try: token = Token.objects.get(key=session_key) except Token.DoesNotExist: raise AuthenticationFailed('Invalid token') return (token.user, token)settings.py:REST_FRAMEWORK: dict[str, str] = { "DEFAULT_AUTHENTICATION_CLASSES": ( "apps.users.authentication.CookieTokenAuthentication", "rest_framework.authentication.SessionAuthentication", ), "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", }
- 给
内容的提问来源于stack exchange,提问作者Miguel Montes
相关产品推荐
相关产品推荐

