2025-10-13 17:52:28 +08:00
|
|
|
|
import hmac
|
|
|
|
|
|
import base64
|
|
|
|
|
|
import hashlib
|
|
|
|
|
|
import time
|
2025-02-13 17:06:48 +08:00
|
|
|
|
|
2025-02-14 11:33:56 +08:00
|
|
|
|
|
2025-02-13 17:06:48 +08:00
|
|
|
|
class AuthenticationError(Exception):
|
|
|
|
|
|
"""认证异常"""
|
2025-10-13 17:52:28 +08:00
|
|
|
|
|
2025-02-13 17:06:48 +08:00
|
|
|
|
pass
|
|
|
|
|
|
|
2025-02-14 11:33:56 +08:00
|
|
|
|
|
2025-02-13 17:06:48 +08:00
|
|
|
|
class AuthMiddleware:
|
2025-12-29 00:09:26 +08:00
|
|
|
|
"""
|
2025-12-29 01:20:38 +08:00
|
|
|
|
认证中间件
|
2025-12-29 00:09:26 +08:00
|
|
|
|
用于 WebSocket/MQTT 连接认证
|
2025-12-29 01:20:38 +08:00
|
|
|
|
集成 AuthManager 的 token 验证逻辑,支持多种认证方式
|
2025-12-29 00:09:26 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, config: dict):
|
|
|
|
|
|
"""
|
|
|
|
|
|
初始化认证中间件
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
config: 配置字典,包含认证相关配置
|
|
|
|
|
|
"""
|
2025-02-13 17:06:48 +08:00
|
|
|
|
self.config = config
|
2025-12-29 01:20:38 +08:00
|
|
|
|
server_config = config.get("server", {})
|
|
|
|
|
|
auth_config = server_config.get("auth", {})
|
|
|
|
|
|
|
2025-12-29 00:09:26 +08:00
|
|
|
|
self.enabled = auth_config.get("enabled", False)
|
|
|
|
|
|
self.tokens = auth_config.get("tokens", [])
|
2025-12-29 01:20:38 +08:00
|
|
|
|
self.allowed_devices = set(auth_config.get("allowed_devices", []))
|
|
|
|
|
|
|
|
|
|
|
|
# 获取 auth_key 用于 HMAC token 验证
|
|
|
|
|
|
self.auth_key = server_config.get("auth_key", "")
|
|
|
|
|
|
expire_seconds = auth_config.get("expire_seconds", None)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建 AuthManager 实例用于 HMAC token 验证
|
|
|
|
|
|
if self.auth_key:
|
|
|
|
|
|
self._auth_manager = AuthManager(
|
|
|
|
|
|
secret_key=self.auth_key,
|
|
|
|
|
|
expire_seconds=expire_seconds
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._auth_manager = None
|
2025-02-14 11:33:56 +08:00
|
|
|
|
|
2025-12-29 01:20:38 +08:00
|
|
|
|
def authenticate(self, device_id: str, token: str = None, client_id: str = None) -> bool:
|
2025-12-29 00:09:26 +08:00
|
|
|
|
"""
|
2025-12-29 01:20:38 +08:00
|
|
|
|
验证设备认证(同步方法)
|
2025-12-29 00:09:26 +08:00
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
device_id: 设备 ID
|
2025-12-29 01:20:38 +08:00
|
|
|
|
token: 认证令牌(可以是静态 token 或 HMAC token)
|
|
|
|
|
|
client_id: 客户端 ID(用于 HMAC token 验证)
|
2025-12-29 00:09:26 +08:00
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
bool: 认证是否通过
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not self.enabled:
|
2025-02-13 17:06:48 +08:00
|
|
|
|
return True
|
2025-12-29 00:09:26 +08:00
|
|
|
|
|
2025-12-29 01:20:38 +08:00
|
|
|
|
# 1. 检查白名单
|
|
|
|
|
|
if device_id and device_id in self.allowed_devices:
|
2025-02-14 11:33:56 +08:00
|
|
|
|
return True
|
2025-12-29 00:09:26 +08:00
|
|
|
|
|
2025-12-29 01:20:38 +08:00
|
|
|
|
# 2. 检查静态 token
|
2025-12-29 00:09:26 +08:00
|
|
|
|
if token:
|
2025-12-29 01:20:38 +08:00
|
|
|
|
# 移除 Bearer 前缀(如果有)
|
|
|
|
|
|
if token.startswith("Bearer "):
|
|
|
|
|
|
token = token[7:]
|
|
|
|
|
|
|
2025-12-29 00:09:26 +08:00
|
|
|
|
for token_config in self.tokens:
|
|
|
|
|
|
if token_config.get("token") == token:
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
2025-12-29 01:20:38 +08:00
|
|
|
|
# 3. 检查 HMAC token(需要 AuthManager)
|
|
|
|
|
|
if token and self._auth_manager and client_id and device_id:
|
|
|
|
|
|
if self._auth_manager.verify_token(token, client_id, device_id):
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
2025-12-29 00:09:26 +08:00
|
|
|
|
return False
|
2025-02-14 11:33:56 +08:00
|
|
|
|
|
2025-12-29 01:20:38 +08:00
|
|
|
|
async def authenticate_async(self, headers: dict) -> bool:
|
|
|
|
|
|
"""
|
|
|
|
|
|
从 headers 中提取信息并进行异步认证
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
headers: HTTP 请求头字典
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
bool: 认证是否通过
|
|
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
|
AuthenticationError: 认证失败时抛出
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not self.enabled:
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
device_id = headers.get("device-id")
|
|
|
|
|
|
client_id = headers.get("client-id")
|
|
|
|
|
|
authorization = headers.get("authorization", "")
|
|
|
|
|
|
|
|
|
|
|
|
# 提取 token
|
|
|
|
|
|
token = None
|
|
|
|
|
|
if authorization:
|
|
|
|
|
|
if authorization.startswith("Bearer "):
|
|
|
|
|
|
token = authorization[7:]
|
|
|
|
|
|
else:
|
|
|
|
|
|
token = authorization
|
|
|
|
|
|
|
|
|
|
|
|
# 执行认证
|
|
|
|
|
|
if self.authenticate(device_id, token, client_id):
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
raise AuthenticationError(f"认证失败: device_id={device_id}")
|
|
|
|
|
|
|
|
|
|
|
|
def authenticate_websocket(self, websocket) -> bool:
|
|
|
|
|
|
"""
|
|
|
|
|
|
WebSocket 连接认证
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
websocket: WebSocket 连接对象
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
bool: 认证是否通过
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not self.enabled:
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
headers = dict(websocket.request.headers)
|
|
|
|
|
|
device_id = headers.get("device-id")
|
|
|
|
|
|
client_id = headers.get("client-id")
|
|
|
|
|
|
authorization = headers.get("authorization", "")
|
|
|
|
|
|
|
|
|
|
|
|
# 提取 token
|
|
|
|
|
|
token = None
|
|
|
|
|
|
if authorization:
|
|
|
|
|
|
if authorization.startswith("Bearer "):
|
|
|
|
|
|
token = authorization[7:]
|
|
|
|
|
|
else:
|
|
|
|
|
|
token = authorization
|
|
|
|
|
|
|
|
|
|
|
|
return self.authenticate(device_id, token, client_id)
|
|
|
|
|
|
|
2025-02-14 11:33:56 +08:00
|
|
|
|
|
2025-10-13 17:52:28 +08:00
|
|
|
|
class AuthManager:
|
|
|
|
|
|
"""
|
|
|
|
|
|
统一授权认证管理器
|
|
|
|
|
|
生成与验证 client_id device_id token(HMAC-SHA256)认证三元组
|
|
|
|
|
|
token 中不含明文 client_id/device_id,只携带签名 + 时间戳; client_id/device_id在连接时传递
|
|
|
|
|
|
在 MQTT 中 client_id: client_id, username: device_id, password: token
|
|
|
|
|
|
在 Websocket 中,header:{Device-ID: device_id, Client-ID: client_id, Authorization: Bearer token, ......}
|
|
|
|
|
|
"""
|
2025-02-14 11:33:56 +08:00
|
|
|
|
|
2025-10-13 17:52:28 +08:00
|
|
|
|
def __init__(self, secret_key: str, expire_seconds: int = 60 * 60 * 24 * 30):
|
|
|
|
|
|
if not expire_seconds or expire_seconds < 0:
|
|
|
|
|
|
self.expire_seconds = 60 * 60 * 24 * 30
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.expire_seconds = expire_seconds
|
|
|
|
|
|
self.secret_key = secret_key
|
2025-02-14 11:33:56 +08:00
|
|
|
|
|
2025-10-13 17:52:28 +08:00
|
|
|
|
def _sign(self, content: str) -> str:
|
|
|
|
|
|
"""HMAC-SHA256签名并Base64编码"""
|
|
|
|
|
|
sig = hmac.new(
|
|
|
|
|
|
self.secret_key.encode("utf-8"), content.encode("utf-8"), hashlib.sha256
|
|
|
|
|
|
).digest()
|
|
|
|
|
|
return base64.urlsafe_b64encode(sig).decode("utf-8").rstrip("=")
|
|
|
|
|
|
|
|
|
|
|
|
def generate_token(self, client_id: str, username: str) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
生成 token
|
|
|
|
|
|
Args:
|
|
|
|
|
|
client_id: 设备连接ID
|
|
|
|
|
|
username: 设备用户名(通常为deviceId)
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
str: token字符串
|
|
|
|
|
|
"""
|
|
|
|
|
|
ts = int(time.time())
|
|
|
|
|
|
content = f"{client_id}|{username}|{ts}"
|
|
|
|
|
|
signature = self._sign(content)
|
|
|
|
|
|
# token仅包含签名与时间戳,不包含明文信息
|
|
|
|
|
|
token = f"{signature}.{ts}"
|
|
|
|
|
|
return token
|
|
|
|
|
|
|
|
|
|
|
|
def verify_token(self, token: str, client_id: str, username: str) -> bool:
|
|
|
|
|
|
"""
|
|
|
|
|
|
验证token有效性
|
|
|
|
|
|
Args:
|
|
|
|
|
|
token: 客户端传入的token
|
|
|
|
|
|
client_id: 连接使用的client_id
|
|
|
|
|
|
username: 连接使用的username
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
sig_part, ts_str = token.split(".")
|
|
|
|
|
|
ts = int(ts_str)
|
|
|
|
|
|
if int(time.time()) - ts > self.expire_seconds:
|
|
|
|
|
|
return False # 过期
|
|
|
|
|
|
|
|
|
|
|
|
expected_sig = self._sign(f"{client_id}|{username}|{ts}")
|
|
|
|
|
|
if not hmac.compare_digest(sig_part, expected_sig):
|
|
|
|
|
|
return False
|
2025-02-14 11:33:56 +08:00
|
|
|
|
|
2025-02-13 17:06:48 +08:00
|
|
|
|
return True
|
2025-10-13 17:52:28 +08:00
|
|
|
|
except Exception:
|
|
|
|
|
|
return False
|