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

基于子域名的django-tenants多租户应用迁移至单URL架构可行性咨询

解决方案:从django-tenants子域名多租户迁移到单URL架构

核心思路

要实现单URL下的租户隔离+统一认证导航,关键是在会话中持久化租户标识,同时让django-tenants能从会话而非子域名识别租户。

具体实现步骤

1. 修改租户中间件逻辑

django-tenants默认通过子域名解析租户,需要自定义中间件替换TenantMiddleware,优先从会话中获取租户schema:

from django_tenants.middleware.main import TenantMiddleware
from django_tenants.utils import get_tenant_model

class SessionBasedTenantMiddleware(TenantMiddleware):
    def get_tenant(self, request):
        # 先从会话取租户标识,没有再尝试子域名(兼容过渡)
        tenant_schema = request.session.get('tenant_schema')
        if tenant_schema:
            TenantModel = get_tenant_model()
            try:
                return TenantModel.objects.get(schema_name=tenant_schema)
            except TenantModel.DoesNotExist:
                pass
        # fallback到原逻辑(可选,用于过渡阶段)
        return super().get_tenant(request)

然后在settings.py替换原中间件:

MIDDLEWARE = [
    # ... 其他中间件
    # 'django_tenants.middleware.main.TenantMiddleware', 注释掉原中间件
    'yourapp.middleware.SessionBasedTenantMiddleware',  # 换成自定义的
]

2. 统一认证入口与会话绑定租户

  • 设计统一的登录页面(放在公共schema的app里),让用户输入租户标识(如租户代码/名称)+账号密码。
  • 认证时先根据租户标识找到对应schema,切换schema后验证用户凭据:
from django_tenants.utils import schema_context
from django.contrib.auth import authenticate, login
from django_tenants.models import Tenant

def unified_login(request):
    if request.method == 'POST':
        tenant_code = request.POST.get('tenant_code')
        username = request.POST.get('username')
        password = request.POST.get('password')
        
        try:
            tenant = Tenant.objects.get(client_name=tenant_code)  # 或按schema_name查询
            with schema_context(tenant.schema_name):
                user = authenticate(request, username=username, password=password)
                if user:
                    login(request, user)
                    # 把租户schema存入会话
                    request.session['tenant_schema'] = tenant.schema_name
                    return redirect('tenant_dashboard')  # 跳转到租户内首页
                else:
                    return render(request, 'login.html', {'error': '账号密码错误'})
        except Tenant.DoesNotExist:
            return render(request, 'login.html', {'error': '租户不存在'})
    return render(request, 'login.html')
  • 注意:登录视图必须放在公共schema的应用中,确保所有租户都能访问。

3. 处理租户内导航的会话维持

  • 所有租户内的视图都会依赖上述中间件,会话中的tenant_schema会自动让django-tenants切换到对应schema,无需在URL中携带schema参数。
  • 退出登录时清除会话中的租户标识:
def unified_logout(request):
    request.session.pop('tenant_schema', None)
    logout(request)
    return redirect('unified_login')

4. 解决会话跨schema的潜在问题

django-tenants的会话默认存储在公共schema的django_session表中(默认配置),无需额外处理。如果之前会话存在租户私有schema,需要迁移会话数据到公共schema,确保登录态能跨schema识别。

关键注意事项

  • 租户标识的安全性:要验证用户输入的租户标识合法性,防止租户越权访问;可在登录时绑定用户与租户的关联关系,确保用户只能登录自己所属的租户。
  • 中间件顺序:自定义租户中间件要放在AuthenticationMiddleware之后,确保会话已初始化后再解析租户。
  • 静态资源与媒体文件:如果租户有独立的静态/媒体资源,需要通过视图动态返回,或在URL中通过租户标识区分(但无需暴露schema名称)。

内容的提问来源于stack exchange,提问作者Gabriel dos Santos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 22:33:16