Files

207 lines
6.4 KiB
Python
Executable File
Raw Permalink 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.
import hmac
import base64
import hashlib
import time
class AuthenticationError(Exception):
"""认证异常"""
pass
class AuthMiddleware:
"""
认证中间件
用于 WebSocket/MQTT 连接认证
集成 AuthManager 的 token 验证逻辑,支持多种认证方式
"""
def __init__(self, config: dict):
"""
初始化认证中间件
Args:
config: 配置字典,包含认证相关配置
"""
self.config = config
server_config = config.get("server", {})
auth_config = server_config.get("auth", {})
self.enabled = auth_config.get("enabled", False)
self.tokens = auth_config.get("tokens", [])
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
def authenticate(self, device_id: str, token: str = None, client_id: str = None) -> bool:
"""
验证设备认证(同步方法)
Args:
device_id: 设备 ID
token: 认证令牌(可以是静态 token 或 HMAC token
client_id: 客户端 ID(用于 HMAC token 验证)
Returns:
bool: 认证是否通过
"""
if not self.enabled:
return True
# 1. 检查白名单
if device_id and device_id in self.allowed_devices:
return True
# 2. 检查静态 token
if token:
# 移除 Bearer 前缀(如果有)
if token.startswith("Bearer "):
token = token[7:]
for token_config in self.tokens:
if token_config.get("token") == token:
return True
# 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
return False
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)
class AuthManager:
"""
统一授权认证管理器
生成与验证 client_id device_id tokenHMAC-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, ......}
"""
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
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
return True
except Exception:
return False