74 lines
2.5 KiB
Python
74 lines
2.5 KiB
Python
"""开放平台认证与授权:
|
|
APIKeyAuthentication 支持第三方系统通过 Header:
|
|
X-API-Key: dh_xxxx.yyyy
|
|
或
|
|
Authorization: Api-Key dh_xxxx.yyyy
|
|
安全访问开放接口。
|
|
"""
|
|
|
|
from rest_framework import authentication, exceptions, permissions
|
|
from django.utils import timezone
|
|
from asgiref.sync import sync_to_async
|
|
from .models import APIKey
|
|
|
|
|
|
class APIKeyAuthentication(authentication.BaseAuthentication):
|
|
"""基于 API Key 的第三方系统鉴权类。"""
|
|
|
|
def authenticate(self, request):
|
|
auth_header = request.META.get("HTTP_AUTHORIZATION", "")
|
|
api_key_header = request.META.get("HTTP_X_API_KEY", "")
|
|
|
|
raw_key = None
|
|
if api_key_header:
|
|
raw_key = api_key_header.strip()
|
|
elif auth_header.startswith("Api-Key "):
|
|
raw_key = auth_header[8:].strip()
|
|
|
|
if not raw_key:
|
|
return None
|
|
|
|
if "." not in raw_key:
|
|
raise exceptions.AuthenticationFailed("API Key 格式无效(必须包含前缀)")
|
|
|
|
prefix = raw_key.split(".")[0]
|
|
key_obj = APIKey.objects.filter(prefix=prefix, is_active=True).select_related("tenant").first()
|
|
if not key_obj:
|
|
raise exceptions.AuthenticationFailed("API Key 不存在或已被禁用")
|
|
|
|
if key_obj.expires_at and key_obj.expires_at < timezone.now():
|
|
raise exceptions.AuthenticationFailed("API Key 已过期")
|
|
|
|
if not key_obj.verify_key(raw_key):
|
|
raise exceptions.AuthenticationFailed("API Key 签名无效")
|
|
|
|
# 记录调用时间并绑定 tenant
|
|
key_obj.last_used_at = timezone.now()
|
|
key_obj.save(update_fields=["last_used_at"])
|
|
|
|
# 注入租户上下文
|
|
request.tenant_obj = key_obj.tenant
|
|
if hasattr(request, "_request"):
|
|
request._request.tenant_obj = key_obj.tenant
|
|
|
|
# 绑定系统用户
|
|
user = key_obj.created_by
|
|
if not user:
|
|
from django.contrib.auth import get_user_model
|
|
user = get_user_model().objects.filter(is_superuser=True).first()
|
|
|
|
return (user, key_obj)
|
|
|
|
|
|
def require_scope(scope_name: str):
|
|
"""用于视图权限检查的 Scope 装饰辅助类。"""
|
|
|
|
class ScopePermission(permissions.BasePermission):
|
|
def has_permission(self, request, view):
|
|
api_key = getattr(request, "auth", None)
|
|
if not isinstance(api_key, APIKey):
|
|
return False # 未携带有效 APIKey 则拒绝访问
|
|
return scope_name in api_key.scopes or "*" in api_key.scopes
|
|
|
|
return ScopePermission
|