使用Django+DRF+Knox时凭证正确却出现401认证问题
我在Django项目中集成Django Rest Knox实现Token认证后,使用正确凭证登录却返回401错误。项目采用自定义User模型(username字段为企业邮箱),仅轻微定制Knox LoginView,当前处于本地WSL调试环境(DEBUG模式),使用Django 4.2.3、Django Rest Framework 3.14.0、Django Rest Knox 4.2.0。通过Insomnia测试时凭证明确存在,但系统提示未提供凭证。
核心需求
- 支持单用户多Token(适配多设备)
- 登出时移除对应Token(安全最佳实践)
- 支持移除用户所有Token(全设备登出)
- Token安全存储(加解密)
- Token自动过期
相关配置代码
settings.py 片段
INSTALLED_APPS = [ 'accounts', 'knox', 'rest_framework', (..) ] # AUTH USER AUTH_USER_MODEL = 'accounts.CustomUser' # REST FRAMEWORK REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication',), } REST_KNOX = { 'SECURE_HASH_ALGORITHM':'cryptography.hazmat.primitives.hashes.SHA512', 'AUTH_TOKEN_CHARACTER_LENGTH': 64, # 默认64位,无需修改 'TOKEN_TTL': timedelta(hours=8), # 默认10小时,改为8小时 'USER_SERIALIZER': 'knox.serializers.UserSerializer', 'TOKEN_LIMIT_PER_USER': None, # 无Token数量限制 'AUTO_REFRESH': False, # 不自动延长Token有效期 'EXPIRY_DATETIME_FORMAT': api_settings.DATETIME_FORMAT, } KNOX_TOKEN_MODEL = 'knox.AuthToken'
自定义LoginView
class CustomLoginView(KnoxLoginView): permission_classes = (AllowAny,) def post(self, request, *args, **kwargs): print("Entered CustomLoginView.post()") # 调试日志 print("Request headers: ", request.META) # 调试日志 print("Request data: ", request.data) # 调试日志 response = super().post(request, *args, **kwargs) if response.status_code == 200: # 登录成功 print("Authentication succeeded. Redirecting...") # 调试日志 return HttpResponseRedirect('/bots/') # 重定向到指定页面 else: print(f"Authentication failed with code {response.status_code}") # 调试日志 return response
urls.py 片段
urlpatterns = [ path('login/', CustomLoginView.as_view(), name='knox_login'), path('logout/', knox_views.LogoutView.as_view(), name='knox_logout'), path('logoutall/', knox_views.LogoutAllView.as_view(), name='knox_logoutall'), re_path(r'api/auth/', include('knox.urls')), ]
模板HTML
<form id="loginForm"> {% csrf_token %} <div class="form-group"> <input type="text" class="input-custom" id="username" name="username" required placeholder="Usuário"> </div> <div class="form-group"> <input type="password" class="input-custom" id="password" name="password" required placeholder="Senha"> </div> <button id="loginButton" type="submit" class="btn btn-block btn-outline-secondary my-3">Entrar</button> </form>
模板JavaScript
<script> document.addEventListener("DOMContentLoaded", function() { const loginForm = document.getElementById("loginForm"); loginForm.addEventListener("submit", function(e) { e.preventDefault(); const username = document.getElementById("username").value; const password = document.getElementById("password").value; console.log("CSRF Token: ", csrfToken); fetch("{% url 'knox_login' %}", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRFToken": csrfToken }, body: JSON.stringify({ username, password }) }) .then(response => response.json()) .then(data => { if (data.token) { // 登录成功后重定向 window.location.href = "{% url 'bots' %}"; } else { // 错误提示 alert("Credenciais inválidas"); } }); }); }); function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } const csrfToken = getCookie('csrftoken'); </script>
排查方向建议
请求数据格式不兼容:
Knox默认LoginView期望接收application/x-www-form-urlencoded格式数据,而当前JS用了application/json。修改请求头为"Content-Type": "application/x-www-form-urlencoded",并将请求body改为new URLSearchParams({ username, password })。自定义User模型适配问题:
检查CustomUser是否正确继承AbstractBaseUser或AbstractUser,确保username字段(企业邮箱)能被Django认证后端识别。可在CustomLoginView中手动调用authenticate(request, username=request.data.get('username'), password=request.data.get('password'))测试认证是否成功。Knox UserSerializer配置错误:
默认knox.serializers.UserSerializer可能无法适配自定义User模型,需自定义序列化器并配置到REST_KNOX中:# accounts/serializers.py from rest_framework import serializers from .models import CustomUser class CustomUserSerializer(serializers.ModelSerializer): class Meta: model = CustomUser fields = ('id', 'username') # 按实际模型字段调整 # settings.py REST_KNOX = { # ...其他配置 'USER_SERIALIZER': 'accounts.serializers.CustomUserSerializer', }URL路由冲突:
同时定义的path('login/', ...)和re_path(r'api/auth/', include('knox.urls'))可能导致路由匹配异常,建议调整结构,比如将自定义login放在api/auth/下,或移除Knox默认login路由。调试日志分析:
查看CustomLoginView打印的request.data是否正确获取到用户名和密码,若为空则说明数据格式不兼容,需调整JS请求方式。
内容的提问来源于stack exchange,提问作者farchettiensis

