- 专辑搜索静态路由优先注册,避免被 /album/{source}/{id}/{page} 捕获
- mg 流改为异步背压队列,慢客户端不再 QueueFull 悬挂,错误中断响应
- 限流移到鉴权外层,401 爆破计入限流
- 酷我 token 重试移出锁,修复 asyncio.Lock 递归死锁
- 补 CORS、5sing 音质、parse _kind、wy 首次登录 cookie、stream 签名溢出
- mg 流直连、lifespan 关闭 aiohttp 会话、cookie 登录路由与 auth_store 健壮性
- 测试改用 aiohttp + 进程内 uvicorn,移除 httpx/TestClient
- 更新 start.bat 与文档
39 lines
1.4 KiB
Python
39 lines
1.4 KiB
Python
"""流代理签名 —— /stream/* 的无头客户端兼容方案。
|
|
|
|
/url 返回流地址时附加 `?exp=<秒级时间戳>&sig=<HMAC-SHA256(authKey, "path:exp") 前 32 位>`。
|
|
与裸 ?key= 的区别:签名值泄露只能在该时效窗口内拉这一条流,不能反推出秘钥、不能调用其它接口。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import hmac
|
|
import time
|
|
|
|
# 流地址有效期(秒),与 mg 流票据 TTL(10 分钟)对齐;过期后客户端重新 /url 即可
|
|
STREAM_SIGN_TTL = 10 * 60
|
|
|
|
|
|
def _sign(auth_key: str, data: str) -> str:
|
|
return hmac.new(auth_key.encode(), data.encode(), hashlib.sha256).hexdigest()[:32]
|
|
|
|
|
|
def sign_stream(auth_key: str, path: str) -> str:
|
|
"""生成流地址的签名参数(path 为不含 query 的路径,如 /stream/mg/123)。"""
|
|
exp = int(time.time() + STREAM_SIGN_TTL)
|
|
return f"exp={exp}&sig={_sign(auth_key, f'{path}:{exp}')}"
|
|
|
|
|
|
def verify_stream_sign(auth_key: str, path: str, exp: object, sig: object) -> bool:
|
|
"""校验流地址签名(常量时间比较 + 时效窗口)。"""
|
|
try:
|
|
exp_num = int(float(str(exp)))
|
|
except (TypeError, ValueError, OverflowError):
|
|
return False
|
|
sig_str = str(sig or "")
|
|
if not sig_str:
|
|
return False
|
|
if time.time() > exp_num: # 过期即拒(客户端重新 /url 取新地址)
|
|
return False
|
|
return hmac.compare_digest(_sign(auth_key, f"{path}:{exp_num}"), sig_str)
|