使用 Rest Framework 令牌解决 Django-Tenant 子域登录错误

使用 Rest Framework 令牌解决 Django-Tenant 子域登录错误
使用 Rest Framework 令牌解决 Django-Tenant 子域登录错误

为什么 Django-Tenants 中的子域登录会中断:一个现实世界的难题

想象一下构建一个多租户 Django 应用程序,其中每个子域服务于不同的租户,无缝集成用户身份验证。一切看起来都很完美——直到子域上的登录页面抛出了可怕的错误 500 内部服务器错误。你挠挠头,想知道为什么 主域 登录工作正常,但子域登录却不行。 🤔

这个问题令人沮丧,因为它感觉像是一个悖论:系统清楚地识别用户,因为您可以登录管理面板。登录后,您可以访问特定于租户的页面,甚至可以成功提交表单。然而,当您点击登录页面时,会出现错误: “意外的令牌” 幕后到底发生了什么?

让我分享一个相关的例子。这就像一栋房子有两扇门——一扇为客人(您的主域),一扇为家人(子域)。客人的门工作正常,但家庭的门被卡住了。您知道密钥是正确的,但锁定机制存在更深层次的问题,例如数据库模式查询中意外的不匹配。

问题的根源在于 Django Rest Framework 的 令牌认证 与相互作用 Django 租户 图书馆。具体来说,根据以下内容查询令牌 公共模式 而不是租户模式,导致 外键违规 错误。让我们深入研究这个问题,找出原因,并修复所有子域的登录门! 🔧

命令 使用示例
schema_context() 允许在多租户 Django 设置中的架构之间切换。示例:with schema_context('tenant_name'):确保操作在指定租户的数据库模式中执行。
authenticate() 使用用户的凭据对用户进行身份验证。示例: user =authenticate(request, username=username,password=password) 检查提供的凭据是否有效。
Token.objects.get_or_create() 检索用户的现有令牌,如果不存在则创建一个。示例:令牌,已创建 = Token.objects.get_or_create(user=user)。
csrf_exempt 禁用特定视图的 CSRF 保护。示例:@csrf_exempt 用于处理外部或非浏览器 API 请求。
connection.tenant.schema_name 检索 Django 多租户应用程序中当前租户的架构名称。示例:tenant_schema_name = connection.tenant.schema_name。
JsonResponse() 返回 JSON 格式的数据作为 HTTP 响应。示例:return JsonResponse({"status": "success", "token": token.key})。
APIClient() Django Rest Framework 测试客户端,允许在测试中模拟 HTTP 请求。示例:self.client = APIClient()。
localStorage.setItem() 将键值对保存在浏览器的本地存储中。示例:localStorage.setItem('token', data.token) 存储令牌以供将来使用。
Swal.fire() 使用 SweetAlert2 库显示警报弹出窗口。示例:Swal.fire({icon: 'error', title: 'Login Failed'}) 显示样式化的错误消息。
TestCase 用于在 Django 中编写单元测试。示例: class TenantLoginTest(TestCase):创建用于特定于架构的登录测试的测试类。

掌握 Django-Tenants 中特定于租户的身份验证

上面提供的脚本解决了多租户 Django 应用程序中的一个关键问题,其中令牌是从 公共模式 而不是适当的租户架构。出现此行为的原因是 Django Rest Framework (DRF) 在与令牌模型交互时不会自动切换架构。为了解决这个问题,我们利用 Django 租户 图书馆的 架构上下文 方法,允许我们在正确的租户模式中显式执行数据库查询。这确保了每个租户的用户身份验证和令牌检索无缝工作,无论是通过主域还是子域访问。如果不进行此调整,则会出现foreignkeyviolation错误,因为系统在错误的架构中查找用户记录。

“dual_login_view”函数演示了如何对用户进行身份验证,同时确保数据库连接指向租户架构。首先,它从请求负载中提取用户名和密码。然后,使用“authenticate”方法验证凭据。如果成功,它会让用户登录并使用 DRF 的“Token.objects.get_or_create()”方法生成令牌。为了确保此查询针对正确的模式,“schema_context”函数包装逻辑,将数据库上下文切换到活动租户模式。这保证了系统可以找到正确的用户和令牌记录,消除架构不匹配错误。

“TenantAwareLoginAPIView”类通过采用 Django Rest Framework 的 APIView 的模块化方法来增强解决方案。它接受包含用户凭据的 POST 请求,使用“authenticate”对其进行验证,并在凭据正确时生成令牌。重要的是,它使用“schema_context”在正确的租户模式中执行所有操作。这种基于类的视图非常适合现代 API 实现,因为它集中了错误处理并提供干净、结构化的响应。例如,返回 JSON 令牌可确保前端可以将其存储在本地存储中并将其用于后续经过身份验证的请求。

在前端,JavaScript 表单提交脚本在向登录端点发出安全且结构化的请求方面发挥着关键作用。它可以防止默认表单行为、验证输入字段,并通过获取 API 请求将凭据与 CSRF 令牌一起发送。收到成功响应后,令牌将存储在“localStorage”中,并且用户将被重定向。如果服务器返回错误,SweetAlert2 库会显示友好的警报消息。这使得用户体验更加流畅并确保正确的错误反馈。例如,当访问租户子域时,使用有效凭据登录的用户将立即看到成功消息并被重定向到应用程序仪表板。 🔒

使用优化的架构查询处理 Django-Tenants 中的子域登录问题

使用 Django ORM 的后端解决方案,具有显式模式选择和错误处理。

# Import necessary libraries
from django.db import connection
from rest_framework.authtoken.models import Token
from django.contrib.auth import authenticate, login
from django.http import JsonResponse
from django_tenants.utils import schema_context
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def dual_login_view(request):
    """Handle login for multi-tenant subdomains with correct schema."""
    if request.method == "POST":
        username = request.POST.get("login")
        password = request.POST.get("password")
        tenant_schema_name = connection.tenant.schema_name
        try:
            # Switch to the correct tenant schema
            with schema_context(tenant_schema_name):
                user = authenticate(request, username=username, password=password)
                if user is not None:
                    login(request, user)
                    # Generate or retrieve token
                    token, created = Token.objects.get_or_create(user=user)
                    return JsonResponse({"status": "success", "token": token.key})
                else:
                    return JsonResponse({"status": "error", "message": "Invalid credentials"}, status=400)
        except Exception as e:
            return JsonResponse({"status": "error", "message": str(e)}, status=500)
    return JsonResponse({"status": "error", "message": "Invalid request method"}, status=405)

使用租户感知模式进行显式令牌管理

用于在多租户架构中登录的模块化且可重用的 Django API 视图。

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth import authenticate
from rest_framework.authtoken.models import Token
from django_tenants.utils import schema_context
class TenantAwareLoginAPIView(APIView):
    """Login endpoint that ensures tenant-aware schema handling."""
    def post(self, request):
        username = request.data.get("username")
        password = request.data.get("password")
        tenant_schema_name = request.tenant.schema_name
        if not username or not password:
            return Response({"error": "Username and password required"}, status=status.HTTP_400_BAD_REQUEST)
        try:
            with schema_context(tenant_schema_name):
                user = authenticate(request, username=username, password=password)
                if user is None:
                    return Response({"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED)
                # Generate or retrieve token for the user
                token, created = Token.objects.get_or_create(user=user)
                return Response({"token": f"Token {token.key}"}, status=status.HTTP_200_OK)
        except Exception as e:
            return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

用于处理子域登录请求的前端脚本

用于处理表单提交和处理租户子域基于令牌的登录的 JavaScript 解决方案。

<script>
document.querySelector('form').addEventListener('submit', function(event) {
    event.preventDefault();
    let form = event.target;
    let formData = new FormData(form);
    fetch("{% url 'tenant_aware_login' %}", {
        method: 'POST',
        body: JSON.stringify(Object.fromEntries(formData)),
        headers: {
            'Content-Type': 'application/json',
            'X-CSRFToken': formData.get('csrfmiddlewaretoken')
        }
    })
    .then(response => {
        if (!response.ok) throw new Error('Server Error');
        return response.json();
    })
    .then(data => {
        if (data.token) {
            localStorage.setItem('token', data.token);
            window.location.href = '/';
        } else {
            Swal.fire({
                icon: 'error',
                title: 'Login Failed',
                text: data.error || 'Invalid credentials'
            });
        }
    })
    .catch(error => {
        console.error('Error:', error);
    });
});
</script>

用于验证架构感知令牌身份验证的单元测试

Python 中的单元测试可确保 API 正确处理架构切换。

from django.test import TestCase
from rest_framework.test import APIClient
from django_tenants.utils import schema_context
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
class TenantLoginTest(TestCase):
    def setUp(self):
        self.client = APIClient()
        with schema_context('test_tenant'):  # Switch to tenant schema
            self.user = User.objects.create_user(username='testuser', password='testpass')
    def test_successful_login(self):
        with schema_context('test_tenant'):
            response = self.client.post('/api/login/', {
                'username': 'testuser',
                'password': 'testpass'
            })
            self.assertEqual(response.status_code, 200)
            self.assertIn('token', response.json())
    def test_invalid_login(self):
        with schema_context('test_tenant'):
            response = self.client.post('/api/login/', {
                'username': 'wronguser',
                'password': 'wrongpass'
            })
            self.assertEqual(response.status_code, 401)
            self.assertIn('error', response.json())

了解特定于租户的令牌查询在多租户 Django 应用程序中的作用

一个主要方面 多租户 Django 应用程序 确保数据库操作始终发生在正确的租户模式中。发生这种情况的问题是因为 Django 的默认行为假定单个共享模式,当在 Django 中找不到令牌或用户时会导致错误。 公共模式。通过利用诸如 schema_context 函数从 Django 租户 库中,我们显式地在模式之间切换以执行特定于租户的查询。这可确保将用户和令牌的身份验证查询定向到正确的架构。

另一个经常被忽视的关键细节是如何 Token.objects.get_or_create() 运行。默认情况下,它在活动数据库模式中查找用户记录。如果当前模式不正确,查询将失败并显示 外键违规 错误。为了解决这个问题,我们确保涉及令牌模型的任何查询都发生在正确的租户架构上下文中。如果不进行此调整,即使是有效的用户也将无法进行身份验证,因为无法在默认架构中找到用户的 ID。

此外,前端代码在与这些后端进程有效通信方面发挥着至关重要的作用。确保 fetch API 发送 CSRF代币 正确处理 JSON 响应至关重要。例如,将 API 调用包装在 try-catch 块中并使用用户友好的库(例如 SweetAlert2 提高可用性。这些增强功能可确保登录流程保持无缝,即使在子域之间切换或遇到特定于架构的错误时也是如此。例如,想象一个 SaaS 平台,其中每个公司(租户)都使用一个子域 - 修复架构上下文可确保每个员工顺利登录而不会中断。 🚀

多租户 Django 登录问题的常见问题

  1. 是什么原因导致 500 内部服务器错误 登录期间?
  2. 发生错误的原因是 Token.objects.get_or_create() 查询错误的模式,导致查找用户记录时不匹配。
  3. 如何确保令牌查询指向正确的租户架构?
  4. 使用 schema_context()Django 租户 库来包装查询执行并切换到正确的模式。
  5. 为什么管理面板可以登录但用户登录失败?
  6. Django 管理员自动调整架构上下文,但自定义视图使用 authenticate() 或者 Token.objects 除非明确配置,否则可能不会。
  7. 如何在前端检索和存储登录令牌?
  8. 使用 fetch API 发送凭据,然后使用存储响应令牌 localStorage.setItem() 用于持久身份验证。
  9. 如何更好地显示登录失败的错误消息?
  10. 使用类似的库实现前端警报 SweetAlert2 通知用户凭据不正确或服务器问题。

确保跨租户子域顺利登录

解决 Django 多租户应用程序中的登录失败问题需要确保所有数据库查询都在正确的架构中运行。通过显式使用架构上下文等工具,我们可以保证从正确的租户数据库中获取用户令牌,从而避免架构冲突。

想象一下,在 SaaS 平台上工作,用户仅在子域上面临登录失败。通过正确的架构切换,这些问题都可以得到解决,从而确保无缝身份验证。采用此修复不仅可以改善 用户体验 还保证每个租户安全、高效的数据访问。 🔧

了解 Django-Tenant 子域问题的来源和参考
  1. 详细文档 Django 租户 库,解释多租户应用程序中的模式管理。可以在: Django 租户文档
  2. 有关令牌身份验证的 Django Rest Framework (DRF) 官方文档。了解更多信息: DRF 令牌认证
  3. 有关在多租户环境中使用 schema_context 的综合指南。发现于: GitHub - Django 租户
  4. 关于在 Django 应用程序中处理 CSRF 令牌的见解: Django CSRF 文档
  5. 设计多租户 SaaS 平台的最佳实践,包括用户身份验证: SaaS Pegasus 多租户指南