You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Django+DRF+Knox时凭证正确却出现401认证问题

Django Rest 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>

排查方向建议

  1. 请求数据格式不兼容:
    Knox默认LoginView期望接收application/x-www-form-urlencoded格式数据,而当前JS用了application/json。修改请求头为"Content-Type": "application/x-www-form-urlencoded",并将请求body改为new URLSearchParams({ username, password })。

  2. 自定义User模型适配问题:
    检查CustomUser是否正确继承AbstractBaseUser或AbstractUser,确保username字段(企业邮箱)能被Django认证后端识别。可在CustomLoginView中手动调用authenticate(request, username=request.data.get('username'), password=request.data.get('password'))测试认证是否成功。

  3. 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',
    }
    
  4. URL路由冲突:
    同时定义的path('login/', ...)和re_path(r'api/auth/', include('knox.urls'))可能导致路由匹配异常,建议调整结构,比如将自定义login放在api/auth/下,或移除Knox默认login路由。

  5. 调试日志分析:
    查看CustomLoginView打印的request.data是否正确获取到用户名和密码,若为空则说明数据格式不兼容,需调整JS请求方式。

内容的提问来源于stack exchange,提问作者farchettiensis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 20:14:50