Files
GVSDSDK/msync/middleware.py
T
TermiNexus 53af9884df refactor(msync): msync 整体搬迁至 SDK 并消除对 gvsds 的依赖
- msync 全部实现迁入 gvsdsdk(models/registry/ogm/transaction/auth +
  Django 集成层 middleware/sio/views/serializers/urls/admin/apps/migrations)
- 新增 gvsdsdk/secrets.py(secrets.ini 读取)与 serializer_base.py
  (PascalCase DRF 序列化器基类)
- msync 0001 迁移改用 gvsdsdk.models.UUIDField 并与模型 deep_deconstruct
  完全对齐(消除 makemigrations 漂移),dependencies 置空
- msync apps.py 移除 gvsds.app_config 软依赖,域名仅经环境变量/settings 解析
- 验证:屏蔽 gvsds 包后 50 个 gvsdsdk 子模块独立导入通过
2026-08-10 22:23:52 +08:00

155 lines
5.0 KiB
Python

"""模块:服务请求验证中间件与权限装饰器
用于验证来自其他微服务的请求,提取服务身份信息。
在 Django 视图中使用 @require_service_scope 装饰器保护端点。"""
import functools
import logging
from django.http import JsonResponse
from rest_framework.response import Response
from gvsdsdk.msync.auth import ValidateAccessToken
logger = logging.getLogger(__name__)
class ServiceAuthMiddleware:
"""服务认证中间件——从请求中提取并验证服务令牌
将验证结果附加到 request 对象:
- request.service_name: 发起请求的服务名称
- request.service_scope: 服务的权限范围列表
- request.service_token: 原始令牌字符串
"""
SERVICE_AUTH_HEADER = 'Authorization'
SERVICE_AUTH_PREFIX = 'Service'
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
request.service_name = None
request.service_scope = []
request.service_token = None
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if auth_header.startswith(f'{self.SERVICE_AUTH_PREFIX} '):
token = auth_header[len(self.SERVICE_AUTH_PREFIX) + 1:]
payload = ValidateAccessToken(token)
if payload:
request.service_name = payload.get('sub')
request.service_scope = payload.get('scope', [])
request.service_token = token
response = self.get_response(request)
return response
def require_service_scope(*required_scopes):
"""视图装饰器——要求请求来自具有指定权限范围的已认证服务
用法:
@require_service_scope('ogm:read')
def my_view(request):
# request.service_name 已设置
...
"""
def decorator(view_func):
@functools.wraps(view_func)
def wrapper(request, *args, **kwargs):
if not request.service_name:
return JsonResponse(
{'code': 401, 'msg': '需要服务认证', 'data': None},
status=401,
)
if '*' not in request.service_scope:
for scope in required_scopes:
if scope not in request.service_scope:
return JsonResponse(
{'code': 403, 'msg': f'缺少权限: {scope}', 'data': None},
status=403,
)
return view_func(request, *args, **kwargs)
return wrapper
return decorator
def require_service_scope_drq(*required_scopes):
"""DRF 视图装饰器——返回 DRF Response 而非 Django JsonResponse"""
def decorator(view_func):
@functools.wraps(view_func)
def wrapper(self, request, *args, **kwargs):
if not request.service_name:
return Response(
{'code': 401, 'msg': '需要服务认证', 'data': None},
status=401,
)
if '*' not in request.service_scope:
for scope in required_scopes:
if scope not in request.service_scope:
return Response(
{'code': 403, 'msg': f'缺少权限: {scope}', 'data': None},
status=403,
)
return view_func(self, request, *args, **kwargs)
return wrapper
return decorator
class ServicePermission:
"""DRF 权限类——用于 ViewSet 的 permission_classes"""
def __init__(self, *required_scopes):
self.required_scopes = required_scopes
def has_permission(self, request, view):
if not request.service_name:
return False
if '*' in request.service_scope:
return True
return all(s in request.service_scope for s in self.required_scopes)
class ServiceTokenAuthentication:
"""DRF 认证类——从请求头中提取并验证服务令牌
配合 ServiceAuthMiddleware 使用,中间件已将验证结果附加到 request 对象。
此类使 DRF 的 request.user 和 request.auth 可用于权限检查。
"""
def authenticate(self, request):
service_name = getattr(request, 'service_name', None)
if not service_name:
return None
# 返回 (user, auth) 元组,使 DRF 认为请求已认证
# service_name 作为 user 标识,service_token 作为 auth
return (ServicePrincipal(request), request.service_token)
def authenticate_header(self, request):
return 'Service'
class ServicePrincipal:
"""服务主体——作为 DRF 认证后的 request.user"""
def __init__(self, request):
self.service_name = request.service_name
self.service_scope = request.service_scope
self.service_token = request.service_token
@property
def is_authenticated(self):
return True
def __str__(self):
return f'Service:{self.service_name}'