将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
相关产品推荐
相关产品推荐

