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

Django中root超级用户登录需supervisor审批的登录异常求助

Django Root用户登录审批流程异常问题

问题背景

项目中有root和supervisor两个超级用户,期望实现的登录审批流程:

  • root登录后跳转至「等待授权」页面
  • 系统向supervisor发送含明文OTP及验证链接的邮件
  • supervisor输入OTP验证通过后,root自动跳转至着陆页,supervisor看到成功提示;验证失败则双方都看到失败提示

当前问题:OTP验证成功后,supervisor被重定向至着陆页,而root的等待页面无任何变化。

核心问题分析

从日志和代码来看,你在verify_otp视图中创建新HttpRequest并尝试登录root的操作仅修改了后端session数据,无法触发root前端页面的跳转;同时直接返回redirect('landingpage')会让supervisor的页面跳转到root的着陆页,不符合流程设计。

解决方案步骤

1. 完善OTP模型字段(可选但建议)

给otp_secret指定长度,避免数据库字段定义不规范:

# models.py
class OTP(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    otp_secret = models.CharField(max_length=32)
    is_verified = models.BooleanField(default=False)

    def __str__(self):
        return self.user.email

class UserSession(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    session = models.ForeignKey(Session, on_delete=models.CASCADE)

2. 重构verify_otp视图逻辑

移除无效的request对象创建操作,改为更新OTP验证状态,给supervisor返回专属提示页:

# views.py
@login_required(login_url='custom_login')
def verify_otp(request):
    logger.debug('inside verify_otp. request.user is %s', request.user)
    otp = request.POST.get('otp')
    logger.debug('inside verify_otp: otp is %s. Request.user is: %s', otp, request.user)

    try:
        latest_root_session = UserSession.objects.filter(user=user2, user__otp__is_verified=False).latest('session__expire_date')
    except ObjectDoesNotExist:
        messages.error(request, '当前无待审批的root用户会话')
        return render(request, 'something_went_wrong.html')

    if latest_root_session.user.id != user2.id:
        messages.error(request, '当前无待审批的root用户会话')
        return render(request, 'something_went_wrong.html')

    # 校验OTP格式
    if not otp or otp == 'None':
        messages.error(request, 'OTP不能为空')
        return render(request, 'something_went_wrong.html')
    try:
        int_otp = int(otp)
    except ValueError:
        messages.error(request, 'OTP格式无效')
        return render(request, 'something_went_wrong.html')

    # 获取OTP对象并验证
    otp_obj = OTP.objects.filter(user__pk=user2.id).first()
    if not otp_obj:
        messages.error(request, 'OTP授权失败,请重新登录')
        return render(request, 'something_went_wrong.html')

    totp = pyotp.TOTP(otp_obj.otp_secret, interval=otp_interval)
    verification_result = totp.verify(int_otp)

    if verification_result:
        # 更新OTP验证状态
        otp_obj.is_verified = True
        otp_obj.save()
        # 给supervisor返回成功提示
        messages.success(request, 'OTP验证成功,root用户已登录')
        return render(request, 'otp_success.html')  # 新建该页面展示成功信息
    else:
        # 标记验证失败状态
        otp_obj.is_verified = False
        otp_obj.save()
        latest_root_session.session['otp_failed'] = True
        latest_root_session.session.save()
        messages.error(request, 'OTP授权失败')
        return render(request, 'something_went_wrong.html')

3. 给root的等待页面添加轮询机制

修改login_redirect.html,通过JavaScript定时检查验证状态:

<!-- templates/login_redirect.html -->
<!DOCTYPE html>
<html>
<head>
    <title>等待授权</title>
</head>
<body>
    <h1>等待Supervisor授权...</h1>
    <div id="status"></div>

    <script>
        // 每3秒轮询一次验证状态
        const poll = setInterval(() => {
            fetch('{% url "check_otp_status" %}')
                .then(res => res.json())
                .then(data => {
                    if (data.status === 'verified') {
                        clearInterval(poll);
                        window.location.href = '{% url "landingpage" %}';
                    } else if (data.status === 'failed') {
                        clearInterval(poll);
                        document.getElementById('status').textContent = 'OTP授权失败,请重新登录';
                    }
                })
                .catch(err => console.error('轮询失败:', err));
        }, 3000);
    </script>
</body>
</html>

4. 添加检查OTP状态的视图

# views.py
from django.http import JsonResponse

@login_required(login_url='custom_login')
def check_otp_status(request):
    # 仅允许root用户访问
    if not is_root_user(request.user):
        return JsonResponse({'status': 'unauthorized'}, status=403)
    
    otp_obj = OTP.objects.filter(user=request.user).first()
    if not otp_obj:
        return JsonResponse({'status': 'pending'})
    
    if otp_obj.is_verified:
        # 验证成功后重置状态,避免重复登录
        otp_obj.is_verified = False
        otp_obj.save()
        return JsonResponse({'status': 'verified'})
    
    # 检查是否标记失败状态
    if request.session.get('otp_failed'):
        del request.session['otp_failed']
        return JsonResponse({'status': 'failed'})
    
    return JsonResponse({'status': 'pending'})

5. 配置路由

在urls.py中新增状态检查路由:

from django.urls import path
from . import views

urlpatterns = [
    # 其他路由...
    path('check-otp-status/', views.check_otp_status, name='check_otp_status'),
    path('enter-otp/', views.verify_otp, name='enter_otp'),
]

关键修改说明

  1. 移除无效操作:删除创建新HttpRequest和调用login的代码,这些操作无法触发前端页面跳转,完全无效。
  2. 轮询触发跳转:通过前端定时轮询后端状态,是跨用户授权场景下触发页面跳转的标准方案。
  3. 分离用户行为:supervisor验证成功后仅看到专属成功提示,不会跳转到root的着陆页;root页面根据轮询结果自动跳转。
  4. 状态重置:验证成功后重置OTP的is_verified状态,避免root下次登录时直接通过。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 20:32:31