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

将Django otp_required装饰器改造为全局生效的OTP校验中间件

需求说明

现有可正常使用的Django OTP校验装饰器,使用时需要给每个视图手动添加@otp_required(login_url='login')注解,希望将其能力整合到现有登录校验中间件中,避免逐个视图添加装饰器。

现有装饰器代码

from django.contrib.auth.decorators import user_passes_test

from django_otp import user_has_device
from django_otp.conf import settings


def otp_required(view=None, redirect_field_name='next', login_url=None, if_configured=False):
    """
    与Django原生的login_required装饰器逻辑类似,额外要求用户完成OTP校验
    默认跳转地址为配置项OTP_LOGIN_URL

    :param if_configured: 如果设为True,已登录但未配置OTP设备的用户可以直接访问
        默认值为False
    :type if_configured: bool
    """
    if login_url is None:
        login_url = settings.OTP_LOGIN_URL

    def test(user):
        return user.is_verified() or (if_configured and user.is_authenticated and not user_has_device(user))

    decorator = user_passes_test(test, login_url=login_url, redirect_field_name=redirect_field_name)

    return decorator if (view is None) else decorator(view)

原有基础登录校验中间件

from django.utils.deprecation import MiddlewareMixin
from django.urls import resolve, reverse
from django.http import HttpResponseRedirect
from wfi_workflow import settings
from django_otp import user_has_device
from django_otp.decorators import otp_required
from django_otp.middleware import is_verified

class LoginRequiredMiddleware(MiddlewareMixin):
    """
    全站登录校验中间件,除豁免路径外所有页面要求用户先登录
    豁免路由可在settings中配置AUTH_EXEMPT_ROUTES元组指定
    """

    #@otp_required(login_url='login')
    def process_request(self, request):
        assert hasattr(request, 'user'), """
        登录校验中间件需要放在AuthenticationMiddleware之后
        同时确保开启模板上下文处理器:'django.contrib.account.context_processors.account'
        """


        if not request.user.is_verified() and not request.path.startswith('/admin/') and not request.path.startswith('/account/' ):

            current_route_name = resolve(request.path_info).url_name

            if not current_route_name in settings.AUTH_EXEMPT_ROUTES:
                return HttpResponseRedirect(reverse(settings.LOGIN_URL))
解决方案

直接替换原有中间件为以下代码即可实现统一的登录+OTP校验:

from django.utils.deprecation import MiddlewareMixin
from django.urls import resolve, reverse
from django.http import HttpResponseRedirect
from wfi_workflow import settings
from django_otp import user_has_device

class LoginAndOTPRequiredMiddleware(MiddlewareMixin):
    """
    全站统一校验中间件,依次校验登录状态和OTP验证状态
    豁免路径可通过settings.AUTH_EXEMPT_ROUTES配置
    """
    def process_request(self, request):
        assert hasattr(request, 'user'), """
        该中间件需要放在AuthenticationMiddleware和django-otp的OTPMiddleware之后
        同时确保开启模板上下文处理器:'django.contrib.account.context_processors.account'
        """
        # 豁免路径直接放行
        if request.path.startswith('/admin/') or request.path.startswith('/account/'):
            return None
        
        current_route_name = resolve(request.path_info).url_name
        if current_route_name in settings.AUTH_EXEMPT_ROUTES:
            return None
        
        # 校验登录状态
        if not request.user.is_authenticated:
            return HttpResponseRedirect(reverse(settings.LOGIN_URL))
        
        # 校验OTP状态,完全复用原有装饰器逻辑
        if_configured = False  # 若需要未配置OTP设备的用户可直接访问,改为True即可
        otp_verified = request.user.is_verified() or (if_configured and not user_has_device(request.user))
        if not otp_verified:
            # 跳转OTP验证页,携带next参数完成验证后跳回原页面
            otp_login_url = reverse(settings.OTP_LOGIN_URL) if hasattr(settings, 'OTP_LOGIN_URL') else reverse('login')
            return HttpResponseRedirect(f"{otp_login_url}?next={request.path}")
        
        return None

配置注意事项

  • 中间件注册顺序必须放在django.contrib.auth.middleware.AuthenticationMiddleware和django_otp.middleware.OTPMiddleware之后,否则无法正常读取用户的OTP相关属性
  • 确保settings中配置了OTP_LOGIN_URL指向你的OTP验证页面路由,未配置则默认使用login路由
  • AUTH_EXEMPT_ROUTES需要包含OTP验证相关的路由名称,避免出现跳转死循环
  • 若需要给特定视图豁免OTP校验,直接将对应路由名称添加到AUTH_EXEMPT_ROUTES即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 00:15:01