Files

59 lines
2.4 KiB
Python

"""开放平台模型:
APIKey 外部系统访问令牌(哈希安全存储、Scope 细粒度权限控制、调用频率与最后使用审计)
"""
import hashlib
import secrets
from django.db import models
from apps.core.base_models import TenantScopedModel
class APIKey(TenantScopedModel):
"""外部第三方对接 API Key。"""
name = models.CharField(max_length=128, help_text="接入方系统名称,如'下游分销A系统'")
prefix = models.CharField(max_length=16, db_index=True, help_text="Key前缀用于检索")
hashed_key = models.CharField(max_length=128, help_text="SHA256 哈希值")
# 权限范围:["products:read", "stocks:read", "orders:write"]
scopes = models.JSONField(default=list, blank=True, help_text="授权的作用域列表")
is_active = models.BooleanField(default=True)
rate_limit = models.IntegerField(default=120, help_text="每分钟最大请求次数")
expires_at = models.DateTimeField(null=True, blank=True, help_text="过期时间")
last_used_at = models.DateTimeField(null=True, blank=True, help_text="最后一次调用时间")
class Meta:
db_table = "openapi_api_key"
verbose_name_plural = "开放平台APIKey"
ordering = ["-created_at"]
def __str__(self):
return f"{self.name} ({self.prefix}****)"
@classmethod
def generate(cls, tenant, name: str, scopes: list = None, expires_at=None, created_by=None):
"""生成并安全存储一个新的 API Key,返回 (instance, raw_key_string)。
注意:raw_key_string 仅在创建时返回一次!
"""
raw_secret = secrets.token_urlsafe(32)
prefix = f"dh_{secrets.token_hex(4)}"
raw_key = f"{prefix}.{raw_secret}"
hashed_key = hashlib.sha256(raw_key.encode("utf-8")).hexdigest()
instance = cls.objects.create(
tenant=tenant,
name=name,
prefix=prefix,
hashed_key=hashed_key,
scopes=scopes or ["products:read", "stocks:read", "orders:write"],
expires_at=expires_at,
created_by=created_by,
)
return instance, raw_key
def verify_key(self, raw_key: str) -> bool:
"""校验原始 Key 与哈希是否匹配。"""
computed = hashlib.sha256(raw_key.encode("utf-8")).hexdigest()
return secrets.compare_digest(self.hashed_key, computed)