refactor: session 改用 itsdangerous,去掉手写密码学原语

为什么:core/session.py 原本手写 HMAC + 自定义 base64 签名,等于自己复刻
itsdangerous 的核心功能——密码学原语自己实现是风险点,且难与上游的安全
修复同步。

关键约束(换库的坑):两者字节级格式并不一致——手写实现用标准 base64 且
保留 '=' 填充,itsdangerous 用 urlsafe 且去填充。若不钉住旧格式,部署瞬间
线上全部登录态会失效。

改法:
- 用 URLSafeTimedSerializer,按 secret 做 lru_cache(maxsize=8),避免每请求
  重建序列化器
- loads() 增加 max_age 参数(默认 None,保持既有「不叠加服务端过期」行为)
- get_uid() 顺带加固:cookie 里塞非整数不再抛 ValueError 变 500,改为返回 None
- 删除 _derive_key / _int_to_bytes 及手写 HMAC/b64

验证:新增 2 例回归测试,其中 legacy cookie 样本由替换前的旧实现在固定时间戳
1700000000 下采集,钉住的是格式而非实现;另一例验证 max_age=None 接受旧
时间戳、max_age=60 拒绝。
This commit is contained in:
snowy-yang committed 2026-09-26 18:53:47 +08:00
1 parent 0b25f215a2
commit 2b4704183c
2 files changed
+63 -47

No files matched your search

+24
View File
@@ -65,6 +65,30 @@ def test_roundtrip_selfconsistency():
assert cs.loads(SECRET, cs.dumps(SECRET, payload)) == payload
def test_legacy_handwritten_cookie_still_loads():
"""旧手写实现签发的 Cookie 仍能被解析(存量会话不必重新登录)。
样本由「替换 itsdangerous 之前」的 core.session.dumps 生成,时间戳固定为
1700000000,故字符串永久有效。它钉住的是**格式**而非实现:旧实现的
payload 用标准 base64 且保留 `=` 填充,签名同理;itsdangerous 用 urlsafe
且去掉填充——两侧必须继续互通,否则线上所有登录态会在部署瞬间失效。
"""
legacy = ('eyJzaXRlX3VpZCI6MjYsInNpdGVfdWlkX3NlcSI6NX0='
'.ZVPxAA.hW2ZBQ0BL1OhI4D4eDKFFVvqGO0')
data = cs.loads(SECRET, legacy)
assert data == {'site_uid': 26, 'site_uid_seq': 5}
assert cs.get_uid(data) == 26
def test_max_age_none_accepts_old_timestamp():
"""默认不校验签发时间(max_age=None)——与既有行为一致,旧 Cookie 不过期。"""
legacy = ('eyJzaXRlX3VpZCI6MjYsInNpdGVfdWlkX3NlcSI6NX0='
'.ZVPxAA.hW2ZBQ0BL1OhI4D4eDKFFVvqGO0')
assert cs.loads(SECRET, legacy) is not None
# 显式给出 max_age 时,超龄的旧 Cookie 必须被拒绝(供未来启用过期用)
assert cs.loads(SECRET, legacy, max_age=60) is None
def test_get_uid_none():
assert cs.get_uid(None) is None
assert cs.get_uid({}) is None