"""JWT 认证 — 签发/验证/中间件""" import json import time import hashlib import hmac as hmac_mod import base64 from typing import Optional from oss.config import get_config from oss.logger.logger import Log class JWTError(Exception): pass class JWTAuth: """JWT 签发与验证(HMAC-SHA256,无外部依赖)""" ALGORITHM = "HS256" HEADER = base64.b64encode(json.dumps({"alg": "HS256", "typ": "JWT"}).encode()).rstrip(b"=").decode() def __init__(self, secret: str = None): config = get_config() self._secret = secret or config.get("JWT_SECRET", "") if not self._secret: self._secret = hashlib.sha256(config.get("API_KEY", "nebula-default-secret").encode()).hexdigest() @staticmethod def _b64url(data: bytes) -> str: return base64.urlsafe_b64encode(data).rstrip(b"=").decode() @staticmethod def _unb64url(data: str) -> bytes: padding = 4 - len(data) % 4 if padding != 4: data += "=" * padding return base64.urlsafe_b64decode(data) def _sign(self, payload_b64: str) -> str: msg = f"{JWTAuth.HEADER}.{payload_b64}".encode() sig = hmac_mod.new(self._secret.encode(), msg, hashlib.sha256).digest() return self._b64url(sig) def issue(self, user_id: str, role: str = "admin", expire_hours: int = 24) -> str: """签发 JWT Token""" payload = { "sub": user_id, "role": role, "iat": int(time.time()), "exp": int(time.time()) + expire_hours * 3600, } payload_b64 = self._b64url(json.dumps(payload).encode()) signature = self._sign(payload_b64) return f"{JWTAuth.HEADER}.{payload_b64}.{signature}" def verify(self, token: str) -> Optional[dict]: """验证 JWT Token,返回 payload 或 None""" try: parts = token.split(".") if len(parts) != 3: return None header_b64, payload_b64, sig_b64 = parts # 验签 expected_sig = self._sign(payload_b64) if not hmac_mod.compare_digest(expected_sig, sig_b64): return None # 解码 payload payload = json.loads(self._unb64url(payload_b64)) # 检查过期 if payload.get("exp", 0) < time.time(): return None return payload except Exception: return None @staticmethod def extract_token(auth_header: str) -> Optional[str]: """从 Authorization 头提取 Bearer Token""" if not auth_header or not auth_header.startswith("Bearer "): return None return auth_header[7:] # ── 快捷方法 ── _auth_instance: Optional[JWTAuth] = None def get_jwt_auth() -> JWTAuth: global _auth_instance if _auth_instance is None: _auth_instance = JWTAuth() return _auth_instance def issue_token(user_id: str, role: str = "admin") -> str: return get_jwt_auth().issue(user_id, role) def verify_token(token: str) -> Optional[dict]: return get_jwt_auth().verify(token)