Django自定义认证后端处理Shopify请求返回401未授权问题
我需要对来自Shopify app bridge的Shopify请求做认证,该机制会将编码后的数据添加到请求头中,再将请求发送至应用的接口端点。
通常应用使用JWT令牌对React前端技术栈发起的请求做认证,但Shopify请求需要采用不同的认证流程:先解码请求头中的数据,再对解码后的数据做合法性校验,最终返回匹配的请求用户。
我编写了如下自定义backend处理上述全部认证流程:
backends.py
from django.contrib.auth.models import User from shopify import session_token class ShopifySessionTokenBackend(BaseBackend): def authenticate(self, request, username=None, password=None, **kwargs): shopify_session_token = request.headers.get("Authorization") try: # Shopify Session Token 校验流程 # ... if shopify_user: return shopify_user.user # 返回对应用户 except Exception as exc: return None def get_user(self, user_id): try: return User.objects.get(pk=user_id) except User.DoesNotExist: return None
请求首先会经过middleware,在中间件中调用authenticate()函数。该函数会收集所有可用的backend,当请求通过全部校验规则时完成请求用户认证,本场景下的校验为Shopify要求的几项请求真实性核验规则。
校验通过后,从数据库中查询到的用户会被赋值给request.user,代表认证流程执行成功,对应自定义中间件代码如下:
middleware.py
from django.contrib.auth import authenticate from django.utils.deprecation import MiddlewareMixin class ShopifyMiddleware(MiddlewareMixin): def process_request(self, request): if not hasattr(request, "user") or request.user.is_anonymous: user = authenticate(request=request) if user: request.user = request._cached_user = user
目前遇到的问题是:接口响应始终返回401 Unauthorized状态码,排查发现是响应返回阶段请求被重新初始化,导致request.user被重置为AnonymousUser,暂未定位到问题根因。
按优先级依次排查以下问题:
中间件顺序配置错误
Django中间件按照配置列表顺序从上到下执行process_request逻辑,返回响应时从下到上执行process_response逻辑。自定义的ShopifyMiddleware必须放在django.contrib.auth.middleware.AuthenticationMiddleware之前,否则Django自带的认证中间件会先将request.user初始化为匿名用户,后续流程触发用户惰性加载时会覆盖手动设置的用户值。
正确配置参考:MIDDLEWARE = [ # 其他前置中间件 "your_app_path.middleware.ShopifyMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", # 其他后置中间件 ]手动赋值用户未触发Django登录绑定逻辑
直接给request.user赋值不会触发Django的用户缓存和持久化逻辑,后续流程调用request.user时会触发惰性加载,从session或其他默认认证源重新拉取用户,覆盖之前设置的值。需要替换手动赋值逻辑,调用Django自带的login()函数完成用户绑定:from django.contrib.auth import authenticate, login from django.utils.deprecation import MiddlewareMixin class ShopifyMiddleware(MiddlewareMixin): def process_request(self, request): if not hasattr(request, "user") or request.user.is_anonymous: user = authenticate(request=request) if user: # 显式指定使用的认证后端,避免Django找不到对应backend抛出异常 login(request, user, backend="your_app_path.backends.ShopifySessionTokenBackend")自定义Backend存在基础配置错误
贴出的代码中ShopifySessionTokenBackend继承了BaseBackend但未导入该基类,会导致Backend注册失败,authenticate()根本不会走到自定义校验逻辑,需要补上导入:from django.contrib.auth.backends import BaseBackend同时确认
settings.py中已经将自定义Backend加入认证后端配置:AUTHENTICATION_BACKENDS = [ "your_app_path.backends.ShopifySessionTokenBackend", "django.contrib.auth.backends.ModelBackend", # 保留默认后台登录认证能力 ]DRF认证组件覆盖了用户值
如果接口使用Django REST Framework开发,DRF的认证逻辑会在Django中间件执行完成后运行,未配置对应认证类时,DRF会重置request.user为匿名用户返回401。需要新增对应DRF认证类:from rest_framework.authentication import BaseAuthentication from django.contrib.auth import authenticate class ShopifySessionAuthentication(BaseAuthentication): def authenticate(self, request): user = authenticate(request=request) if user: return (user, None) return None将该类加入DRF全局认证配置,或在对应视图中通过
authentication_classes属性指定即可。跨域凭证配置缺失
若前端为跨域请求,未开启凭证传递配置时,即使后端认证成功,后续请求也不会携带认证信息,Django会持续将用户识别为匿名用户。前端请求需要开启credentials: 'include'参数,后端django-cors-headers配置同步开启凭证支持,注意开启凭证时不能配置全源允许,需要指定可信域名:CORS_ALLOW_CREDENTIALS = True CORS_ALLOWED_ORIGINS = [ "https://your-shopify-store.myshopify.com", ]
内容的提问来源于stack exchange,提问作者thepylot

