bigywater.tech
Flask · 187

Secret Key 轮换

SECRET_KEY 迟早要换(泄漏、人员变动、定期轮换)。直接换会让所有人退出登录,多台实例滚动发布时还会出现「刚登录又掉线」。这篇用真实的服务验证 Flask 的 SECRET_KEY_FALLBACKS、滚动发布的正确顺序,以及它覆盖不到的地方(SECRET_KEY 的用法见第 119 篇,环境变量见第 184 篇)。

user=None
换 key 且无 fallback 后,旧 cookie 的 /me 结果
两步走
先让所有实例认新 key,再切换签名 key
BadTimeSignature
itsdangerous 令牌在只用新 key 校验时的错误

直接换 key

实测 · 本机执行输出
  旧 key 的服务:用旧 cookie 请求 /me → user='ann'
  换成新 key 重启后:用同一个旧 cookie → user=None(session 被当作空的,用户退出登录),响应是否重新下发 cookie:False

上一篇:生产环境 DEBUG=False;下一篇:数据库连接健康检查。

实验的两个实例是真实的 gunicorn 进程,cookie 在它们之间手工传递。

SECRET_KEY_FALLBACKS

实测 · 本机执行输出
  [session 只读,不修改(默认会话 cookie)] 新 key + fallback 读旧 cookie → user='ann';响应重新下发 cookie:False(cookie 还是旧 key 签的)
  [permanent session(每次请求刷新过期时间)] 新 key + fallback 读旧 cookie → user='ann';响应重新下发 cookie:True;新下发的 cookie 能被只有新 key 的服务读出:'ann'

滚动发布

实测 · 本机执行输出
  做法 1:一步到位(A 旧 key,B 新 key + fallback 旧)
     A 签的 cookie 给 B → 'ann';B 签的 cookie 给 A → None
  做法 2 第一步:全体 SECRET_KEY=旧,FALLBACKS=[新](先让所有实例能认新 key)
     A 签的 cookie 给 B → 'ann';B 签的 cookie 给 A → 'ann'
  做法 2 第二步:全体 SECRET_KEY=新,FALLBACKS=[旧]
     A 签的 cookie 给 B → 'ann';B 签的 cookie 给 A → 'ann'

自签令牌

实测 · 本机执行输出
  新 key + SECRET_KEY_FALLBACKS 配了旧 key,但令牌校验代码只用 app.config["SECRET_KEY"]:HTTP 400 {"error":"BadTimeSignature"}
  校验代码改成 URLSafeTimedSerializer([新 key, 旧 key]):HTTP 200 {"ok":{"uid":7}}

分步演示

Secret Key 轮换
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。gunicorn(两个实例)、itsdangerous
    • 没有验证:Flask-WTF 的 CSRF 令牌与 Flask-Login 的 remember-me cookie 在换 key 后的行为、JWT 签名密钥的轮换(JWT 登录见第 109 篇)、密钥管理服务(KMS、Vault)、旧 cookie 到期时间的具体设置、同一 key 下 session 的 Cookie 大小
    京ICP备2026067502号-1京公网安备11010502063487号