Files
NebulaShell/oss/core/security/jwt_auth.py
Starlight-apk 5e957096fa
Some checks failed
CI / test (3.10) (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / test (3.13) (push) Has been cancelled
feat: Phase 1 - 安全中间件 + 运维工具箱
新增 oss/core/security/ 模块(852行):
- jwt_auth.py: JWT签发/验证(HMAC-SHA256,零外部依赖)
- csrf.py: CSRF Token生成与校验
- input_validator.py: JSON Schema校验+类型强制
- tls.py: 自签名证书生成+SSL上下文

新增 oss/core/ops/ 模块:
- health.py: 增强版/health端点(CPU/内存/磁盘/运行时间)
- metrics.py: Prometheus兼容/metrics端点

对接改造:
- engine.py: 导出新模块
- manager.py: 注册/api/login /health /metrics路由
- middleware.py: CSRF+InputValidation中间件
- config.py: JWT_SECRET/CSRF_SECRET等配置项
- security.py→security/__init__.py: 合并插件沙箱与HTTP安全
2026-05-17 15:42:40 +08:00

107 lines
3.1 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.
"""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)