Files
SMANAGE/gvsds/apps/msync/auth.py
T
2026-06-16 11:55:14 +08:00

173 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""模块:服务间认证
Django ORM 依赖的认证函数,纯函数已移至 gvsdsdk.msync.auth。
"""
import traceback
import datetime
import logging
from gvsdsdk.msync.auth import (
SignPayload,
VerifySignature,
GenerateChallenge,
BuildAuthPayload,
SignAuthPayload,
VerifyAuthPayload,
ServiceTokenEncoder,
)
logger = logging.getLogger(__name__)
def _get_master_secret():
from gvsds.secrets import get as _secret_get
secret = _secret_get('msync', 'MASTER_SECRET', fallback=None)
if not secret:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured('msync MASTER_SECRET 未配置,请在 secrets.ini 或环境变量 MSYNC_MASTER_SECRET 中设置')
return secret
def IssueServiceToken(service_node, access_lifetime_hours=2, refresh_lifetime_days=30):
"""为服务节点签发令牌对(AccessToken + RefreshToken)"""
from gvsds.apps.msync.models import ServiceToken
master_secret = _get_master_secret()
now_aware = datetime.datetime.now(datetime.timezone.utc)
access_expires_aware = now_aware + datetime.timedelta(hours=access_lifetime_hours)
refresh_expires_aware = now_aware + datetime.timedelta(days=refresh_lifetime_days)
now_naive = now_aware.replace(tzinfo=None)
access_expires = access_expires_aware.replace(tzinfo=None)
refresh_expires = refresh_expires_aware.replace(tzinfo=None)
service_scope = service_node.ServiceScope
if service_scope is None:
service_scope = ['ogm:read', 'ogm:write', 'registry:read']
elif isinstance(service_scope, str):
service_scope = [service_scope]
encoder = ServiceTokenEncoder(master_secret)
access_token = encoder.Encode(
service_name=service_node.ServiceName,
scope=service_scope,
expires_at=access_expires_aware,
)
refresh_token = encoder.Encode(
service_name=service_node.ServiceName,
scope=service_scope,
expires_at=refresh_expires_aware,
)
import uuid
token = ServiceToken(
TokenUUID=uuid.uuid4().bytes,
ServiceUUID=service_node,
AccessToken=access_token,
RefreshToken=refresh_token,
TokenScope=service_scope,
ExpiresAt=access_expires,
RefreshExpiresAt=refresh_expires,
)
token.save()
return token
def AuthenticateService(service_name, signed_payload, signature):
"""验证服务身份并签发令牌"""
from gvsds.apps.msync.models import ServiceNode, ServiceToken
try:
node = ServiceNode.objects.get(ServiceName=service_name, ServiceStatus=1)
except ServiceNode.DoesNotExist:
logger.error(f'[AUTH] 服务节点不存在或未激活: {service_name}')
return None
except Exception as e:
logger.error(f'查询服务节点异常 [{service_name}]: {e}\n{traceback.format_exc()}')
return None
import time
ts = signed_payload.get('ts', 0)
now = time.time()
ts_diff = abs(now - ts)
if ts_diff > 300:
logger.error(f'[AUTH] 时间戳校验失败: ts={ts}, now={now}, diff={ts_diff:.1f}s')
return None
raw_secret = node.GetSecret()
if raw_secret is None:
logger.error(f'服务 {service_name} 密钥加密副本缺失,请重新注册')
return None
# 调试:对比签名
from gvsdsdk.msync.auth import SignAuthPayload
expected_sig = SignAuthPayload(signed_payload, raw_secret)
logger.info(f'[AUTH] 签名对比: expected={expected_sig}, actual={signature}, match={expected_sig == signature}')
if not VerifyAuthPayload(signed_payload, signature, raw_secret):
logger.error(f'[AUTH] 签名验证失败: service={service_name}, payload={signed_payload}')
return None
try:
ServiceToken.objects.filter(ServiceUUID=node, TokenStatus=1).update(TokenStatus=0)
except Exception as e:
logger.error(f'撤销旧令牌异常 [{service_name}]: {e}')
try:
return IssueServiceToken(node)
except Exception as e:
logger.error(f'签发服务令牌异常 [{service_name}]: {e}\n{traceback.format_exc()}')
return None
def ValidateAccessToken(access_token):
"""验证 AccessToken 并返回载荷信息"""
from gvsds.apps.msync.models import ServiceToken
master_secret = _get_master_secret()
encoder = ServiceTokenEncoder(master_secret)
payload = encoder.Decode(access_token)
if payload is None:
return None
try:
token_record = ServiceToken.objects.get(AccessToken=access_token, TokenStatus=1)
if token_record.IsExpired():
token_record.Revoke()
return None
except ServiceToken.DoesNotExist:
return None
return payload
def RefreshServiceToken(refresh_token):
"""使用 RefreshToken 刷新令牌对"""
from gvsds.apps.msync.models import ServiceToken, ServiceNode
master_secret = _get_master_secret()
encoder = ServiceTokenEncoder(master_secret)
payload = encoder.Decode(refresh_token)
if payload is None:
return None
try:
old_token = ServiceToken.objects.get(RefreshToken=refresh_token, TokenStatus=1)
except ServiceToken.DoesNotExist:
return None
if old_token.IsRefreshExpired():
old_token.Revoke()
return None
service_name = payload.get('sub')
try:
node = ServiceNode.objects.get(ServiceName=service_name, ServiceStatus=1)
except ServiceNode.DoesNotExist:
return None
old_token.Revoke()
return IssueServiceToken(node)