十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Payload 如何零停机轮换 PAYLOAD_SECRET 并用 rotateSecret 重加密存量数据

Payload 如何零停机轮换 PAYLOAD_SECRET 并用 rotateSecret 重加密存量数据 Payload 如何零停机轮换 PAYLOAD_SECRET 并用 rotateSecret 重加密存量数据【免费下载链接】payloadPayload is the open-source, fullstack Next.js framework, giving you instant backend superpowers. Get a full TypeScript backend and admin panel instantly. Use Payload as a headless CMS or for building powerful applications.项目地址: https://gitcode.com/GitHub_Trending/pa/payloadPAYLOAD_SECRET在 Payload 中承担三件事加密静态存储的敏感数据如 API keys、为 API keys 生成用于查找的索引、以及签发认证令牌。密钥泄露后或者按定期轮换策略你都需要更换它。直接换掉会导致已有会话和 API keys 立刻失效。Payload 提供了previousSecrets配置和rotateSecret工具函数前者让旧密钥在轮换期间继续被接受用于读取从而做到零停机后者负责把旧密钥加密的存量数据重新加密到新密钥下。轮换会影响什么在动手前先明确边界这决定了轮换期间哪些东西可以继续工作密码登录不受影响。密码使用每个用户独立的随机盐做哈希全程不涉及PAYLOAD_SECRET轮换不影响用户名/邮箱 密码登录。JWT 会话是用 secret 签名的。只要旧 secret 还在previousSecrets中用它签发的 token 依然能通过验证活动会话在轮换后继续有效。把旧 secret 从配置中移除后这些 token 将不再被验证用户需要重新登录。加密的 API keys以密文加apiKeyIndex查找值的形式存储两者都在写入时由 secret 派生。旧 secret 在previousSecrets中时按旧密钥建立的索引仍能通过认证rotateSecret会把它们重新密钥化到新 secret。静态加密值API keys 以及你用payload.encrypt加密的任何字段只有在能解密它的 secret 位于密钥环中时才可读。某个值对应的 secret 已不在密钥环中时读取会直接抛错——所以在rotateSecret完成重新密钥化之前务必保留旧 secret。注意与 API Key Strategy 文档 中警告的关系如果直接更换PAYLOAD_SECRET而不使用本文流程存量 API keys 将不再有效、需要重新生成。本流程用previousSecrets换取了一个旧密钥仍可读的过渡窗口避免这个硬中断。准备条件需要两个环境变量分别在配置与迁移中引用PAYLOAD_SECRET新的密钥轮换后的活跃 secretOLD_PAYLOAD_SECRET旧密钥即存量数据当前加密所用的原始PAYLOAD_SECRET值。另外按 Migrations 文档 的要求项目package.json中需要有payload脚本Payload 不应全局安装迁移命令通过包管理器执行{ scripts: { payload: cross-env PAYLOAD_CONFIG_PATHsrc/payload.config.ts payload } }第 1 步把旧 secret 加入 previousSecrets 并部署在 Payload 配置中把新密钥设为活跃secret同时把旧密钥放入previousSecrets然后部署export default buildConfig({ secret: process.env.PAYLOAD_SECRET, // the new secret previousSecrets: [process.env.OLD_PAYLOAD_SECRET], // still accepted for reads // ... })previousSecrets是一个历史secret值的数组轮换期间这些密钥仍会被接受用于读取——验证 JWT、匹配 API keys、解密已存储的值。新数据一律用活跃secret写入。部署后新的写入走新密钥而既有会话、API keys 和加密值因为旧密钥仍在密钥环中而保持可用。这就是零停机的核心。文档对此有明确警告位于previousSecrets中的密钥仍被接受用于认证——泄露的密钥可以伪造 token、以用户身份行事并且仍能解密用其写入的数据。因此必须尽快完成rotateSecret并将其移除不要让旧密钥在密钥环中长期存在。第 2 步创建调用 rotateSecret 的迁移npm run payload migrate:create rotate-secret生成的迁移文件默认存放于./src/migrations中调用rotateSecretimport type { MigrateUpArgs } from payloadcms/db-mongodb import { rotateSecret } from payload export async function up({ payload }: MigrateUpArgs): Promisevoid { const { migrated, skipped } await rotateSecret({ payload, oldSecret: process.env.OLD_PAYLOAD_SECRET, }) payload.logger.info( rotateSecret: migrated ${migrated}, skipped ${skipped}, ) }rotateSecret会重新密钥化所有配置了useAPIKey的 auth collection 中内置的apiKey/apiKeyIndex字段。其参数为选项说明payloadPayload 实例迁移中即args.payloadoldSecret存量数据加密所用的上一代原始PAYLOAD_SECRET。必填collections可选限定轮换范围的 collection slug 数组默认覆盖所有useAPIKeycollectionbatchSize每批处理的文档数默认100dryRun为true时只校验每行、不写入。默认false返回{ migrated, skipped }两个计数migrated为从旧密钥切换到当前密钥的文档数skipped为已处于当前密钥下的文档数安全重跑时计入。第 3 步先用 dry run 校验在真正执行迁移前先以dryRun: true调用一次。它会用旧密钥和当前密钥读取并校验每一行但不写入任何内容如果oldSecret传错它会在做任何更改之前抛出错误await rotateSecret({ payload, oldSecret: process.env.OLD_PAYLOAD_SECRET, dryRun: true, })校验通过不抛错说明oldSecret与存量数据匹配可以进入正式执行。第 4 步执行迁移npm run payload migrate执行完成后日志会输出文档给出的计数信息示例格式来自源文档rotateSecret: migrated 12, skipped 3migrated与skipped两个计数相加应覆盖你预期需要重新密钥化的行数。也可以用npm run payload migrate:status查看该迁移是否已标记为已执行。rotateSecret具备两个特性使中断后可以直接重跑幂等再执行一次会跳过已迁移到当前密钥的行fail-closed任何一行既匹配旧密钥又匹配当前密钥时运行会在写入该行之前中止。同一轮中此前已迁移的行保持正确修正密钥后直接重跑即可。如果oldSecret与当前 secret 相同函数会给出警告并直接返回不做任何事见 rotateSecret 源码警告文案为oldSecret matches the current secret - nothing to rotate. Did you forget to set the new PAYLOAD_SECRET?——出现它说明新密钥没有被正确设置为活跃secret。fail-closed 中止时的报错则形如rotateSecret: could not verify apiKey for collection ... id ... against the provided oldSecret or the current secret. Aborting; rows already migrated in this run are safe to keep - fix the secret and re-run.第 5 步移除旧 secret 并部署全部数据重新密钥化后把旧 secret 从previousSecrets中删除并部署。真正堵住泄露密钥的就是这一步——这正是轮换的最终目的。移除后旧密钥签名的 JWT 和旧密钥索引的 API keys 不再被接受。可选给 previousSecrets 设置过期窗口Payload 不会自动跟踪previousSecrets条目的过期时间数组在配置加载时原样读取。如果想让旧密钥在一段时间后自动失效可以在配置中用你控制的日期做门控例如用OLD_PAYLOAD_SECRET_EXPIRATION环境变量保存旧密钥进入轮换的日期const oneMonth 1000 * 60 * 60 * 24 * 30 // The date the previous secret was put into rotation, e.g. 2026-08-06. const previousSecretAddedAt process.env.OLD_PAYLOAD_SECRET_EXPIRATION ? new Date(process.env.OLD_PAYLOAD_SECRET_EXPIRATION).getTime() : 0 // Accept the previous secret only for one month after it was added. const previousSecrets process.env.OLD_PAYLOAD_SECRET previousSecretAddedAt Date.now() - oneMonth ? [process.env.OLD_PAYLOAD_SECRET] : [] export default buildConfig({ secret: process.env.PAYLOAD_SECRET, previousSecrets, // ... })这个检查只在配置加载时启动/构建时执行一次不会在运行时持续生效一个在密钥仍有效期间启动的长驻进程会把它留在密钥环中直到进程重启——过期只在下次启动或部署时生效。窗口到期后如需让旧密钥真正退出要触发一次重启/重新部署并且务必在窗口关闭之前跑完rotateSecret避免留下只有已过期密钥才能解密的数据。自加密字段的重新密钥化rotateSecret只处理内置的apiKey/apiKeyIndex字段。如果你自己用payload.encrypt加密过其他字段例如自定义加密字段用payload.reencrypt处理——它用旧密钥解密、用当前密钥重新加密const next payload.reencrypt(storedValue, { oldSecret: process.env.OLD_PAYLOAD_SECRET, })payload.encrypt和payload.decrypt也接受显式的{ secret }参数来覆盖密钥secret为原始PAYLOAD_SECRET值const raw payload.decrypt(storedValue, { secret: process.env.OLD_PAYLOAD_SECRET, }) const next payload.encrypt(raw)迁移自加密字段时有一个硬性要求读写存储值要在数据库层payload.db.*进行而不是走 Local API。字段钩子在写入时加密、读取时解密若以当前密钥运行会损坏仍用旧密钥加密的数据。与rotateSecret依赖apiKeyIndex逐行校验、可安全重跑不同手写的reencrypt循环不是幂等的也没有办法区分已迁移的值和传错了oldSecret对已重新密钥化到活跃密钥的 v1 值它会抛错而对旧版 AES-256-CTR 值错误的oldSecret不会抛错——会解密出乱码并静默地重新加密它。所以不要试图用先用当前密钥解密来做保护对旧值不可靠正确做法是自行记录哪些行已迁移、只跑一次、并确保oldSecret正确。加密格式背景新版本写入的加密值使用带认证的信封格式v1:keyId:iv:authTag:ciphertextAES-256-GCM。keyId是一个非机密指纹用于从密钥环中选取正确的密钥认证标签保证用未知或错误密钥加密的值在解密时抛错而不是返回乱码。Payload 旧版本写入的值AES-256-CTR、无前缀仍会被透明读取并在重新加密时例如由rotateSecret触发升级为 v1 信封。这也是rotateSecret能逐行校验新旧密钥匹配的基础。限制小结轮换窗口内旧密钥仍可用于认证与解密窗口结束的唯一方式是完成rotateSecret后从previousSecrets移除它previousSecrets没有内置过期机制过期门控依赖你自己的配置且只在启动/构建时生效一次。若rotateSecret中途 fail-closed 中止修正密钥后重跑即可已迁移的行安全保留。手写的reencrypt循环不可重跑必须自行跟踪已迁移的行。【免费下载链接】payloadPayload is the open-source, fullstack Next.js framework, giving you instant backend superpowers. Get a full TypeScript backend and admin panel instantly. Use Payload as a headless CMS or for building powerful applications.项目地址: https://gitcode.com/GitHub_Trending/pa/payload创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表