"""MSYNC 认证模块 提供 HMAC-SHA256 签名验证、服务令牌编解码等纯函数。 Django ORM 依赖的函数(AuthenticateService, IssueServiceToken 等) 仍在 gvsds.apps.msync.auth 中,由服务端调用。 """ import hmac import hashlib import time import json import base64 import uuid import logging import datetime logger = logging.getLogger(__name__) def SignPayload(Payload, Secret): """使用 HMAC-SHA256 对载荷签名""" if isinstance(Payload, dict): Payload = json.dumps(Payload, sort_keys=True, separators=(',', ':')) if isinstance(Payload, str): Payload = Payload.encode('utf-8') return hmac.new(Secret.encode('utf-8'), Payload, hashlib.sha256).hexdigest() def VerifySignature(Payload, Signature, Secret): """验证 HMAC-SHA256 签名""" Expected = SignPayload(Payload, Secret) return hmac.compare_digest(Expected, Signature) def GenerateChallenge(): """生成认证挑战字符串(时间戳 + 随机数)""" return f'{time.time():.6f}:{uuid.uuid4().hex}' def BuildAuthPayload(ServiceName, Challenge, Timestamp=None): """构建认证请求载荷""" if Timestamp is None: Timestamp = int(time.time()) return { 'service': ServiceName, 'challenge': Challenge, 'ts': Timestamp, } def SignAuthPayload(Payload, ServiceSecret): """对认证载荷签名""" Canonical = json.dumps(Payload, sort_keys=True, separators=(',', ':')) return SignPayload(Canonical, ServiceSecret) def VerifyAuthPayload(Payload, Signature, ServiceSecret): """验证认证载荷签名""" Canonical = json.dumps(Payload, sort_keys=True, separators=(',', ':')) return VerifySignature(Canonical, Signature, ServiceSecret) class ServiceTokenEncoder: """服务令牌编解码器——自包含令牌,无需数据库查询即可验证""" ALGORITHM = 'HS256' ISSUER = 'MSYNC' def __init__(self, SecretKey=None): self.SecretKey = SecretKey or '' def Encode(self, service_name=None, scope=None, expires_at=None, master_secret=None, **kwargs): """编码令牌 支持两种调用方式: 1. 位置参数: Encode(payload_dict) — payload 包含所有字段 2. 关键字参数: Encode(service_name=..., scope=..., expires_at=..., master_secret=...) """ # 如果传入的是字典作为第一个位置参数 if service_name is not None and isinstance(service_name, dict) and scope is None: Payload = service_name Secret = master_secret or self.SecretKey else: Secret = master_secret or self.SecretKey Payload = { 'iss': self.ISSUER, 'sub': service_name, 'scope': scope or [], } if expires_at is not None: if hasattr(expires_at, 'timestamp'): Payload['exp'] = int(expires_at.timestamp()) else: Payload['exp'] = int(expires_at) Header = json.dumps({'alg': self.ALGORITHM, 'typ': 'JWT'}) PayloadData = json.dumps(Payload, sort_keys=True, separators=(',', ':'), default=str) HeaderB64 = base64.urlsafe_b64encode(Header.encode()).rstrip(b'=').decode() PayloadB64 = base64.urlsafe_b64encode(PayloadData.encode()).rstrip(b'=').decode() SigningInput = f'{HeaderB64}.{PayloadB64}' Signature = hmac.new( Secret.encode('utf-8'), SigningInput.encode('utf-8'), hashlib.sha256, ).hexdigest() return f'{SigningInput}.{Signature}' def Decode(self, Token, SecretKey=None): """解码并验证令牌""" Secret = SecretKey or self.SecretKey try: Parts = Token.split('.') if len(Parts) != 3: return None HeaderB64, PayloadB64, Signature = Parts SigningInput = f'{HeaderB64}.{PayloadB64}' ExpectedSig = hmac.new( Secret.encode('utf-8'), SigningInput.encode('utf-8'), hashlib.sha256, ).hexdigest() if not hmac.compare_digest(ExpectedSig, Signature): return None Padding = 4 - len(PayloadB64) % 4 if Padding != 4: PayloadB64 += '=' * Padding PayloadJSON = base64.urlsafe_b64decode(PayloadB64).decode('utf-8') return json.loads(PayloadJSON) except Exception: return None