2. 凭据类型与生命周期:密码、、证书、令牌
凭据管理,说白了就是跟“钥匙”打交道。你手里有多少把钥匙?能开哪些门?什么时候该换锁?这些问题在日志追踪里全都绕不过去。我这些年排查过的安全事故,十有八九都跟凭据泄漏有关——不是密钥写死在代码里,就是证书过期没人管。
这一章,咱们把四种最常见的凭据掰开揉碎:密码、、证书、令牌。我会从创建、存储、轮换三个阶段讲,最后再画一张生命周期全景图。
2.1 密码 —— 最古老也最脆弱的凭据
密码人人会用,但用好的人真不多。我在项目中遇到过一家公司,所有服务器都用一个密码,运维脚本里明文写着“Admin@2020”——结果被实习生批量扫出来了。嗯,别笑,这种事情每天都在发生。
创建
- 长度优先:至少16位。别依赖大小写加数字那套老公式,长密码比复杂密码更抗暴力破解。
- 随机生成:禁止用企业名+年份+特殊符号这种可预测组合。
- 避免弱口令:直接拉黑常见密码列表(如 rockyou.txt 里的前1000个)。
存储
不要存明文。我建议使用 bcrypt、scrypt、Argon2 这类慢哈希算法。如果你的系统现在还在用 MD5 或 SHA-1 存密码,赶紧停——那跟裸奔没区别。
密码存储黄金规则:每个密码加独立随机盐(salt),哈希时间控制在 50~100ms。别为了性能牺牲安全。
轮换
传统做法是每90天强制改密。但我的经验是:如果密码足够随机且没泄漏,频繁轮换反而容易让用户写在便签上。更好的策略是:只在确认泄漏或人员变动时强制轮换,其余时间依赖多因素认证。
2.2 —— 无状态的“通行证”
在微服务架构里几乎无处不在。它不像密码需要交互式登录,通常是一串很长的高熵字符串。我在一次审计中发现,一个团队把AWS Secret Key直接写在GitHub公共仓库里,CI/CD脚本里 export 一下就完事了……那感觉就像把家门钥匙挂在门口。
创建
- 使用加密安全的随机数生成器(如
secrets._hex(32))。 - 密钥前缀可以加版本或环境标识(如
sk-prod-xxxx),方便日志过滤。 - 每次创建时立刻生成密钥指纹(SHA-256 hash),用于后续日志关联。
存储
最佳实践是放到专用的凭据管理服务里(例如 HashiCorp Vault、AWS Secrets Manager)。如果你不得不在本地文件里存放,记得把文件权限设为 600,并启用文件系统加密。
os.environ.get("") 读环境变量,结果 env 文件不小心被提交到代码库了。解决方案:用 python-dotenv 加载 .env 文件,但必须将 .env 写入 .gitignore。
轮换
的轮换策略要区分场景:
| 场景 | 轮换周期 | 备注 |
|---|---|---|
| 生产环境 | 每90天 | 支持新旧密钥并行24h |
| 测试/开发 | 每180天 | 若在隔离环境可延长 |
| 事中(泄漏) | 立即轮换 | 先吊销旧密钥,再部署新密钥 |
2.3 证书 —— 信任链的基石
证书(X.509)用于TLS/mTLS通信。它的生命周期比密码更复杂,因为涉及CA、私钥、有效期、吊销状态。我记得有一次线上服务突然报“证书过期”,查了才发现是某个内部CA签发的设备证书到期了,而监控只盯着公网证书。从那以后,我强制要求所有证书的过期时间必须纳入日志告警。
创建
- 私钥必须使用2048位以上的RSA或ECDSA P-256。
- 证书通用名称(CN)应当与服务域名或内部FQDN严格对应,方便日志追溯。
- 建议启用证书透明度(CT)日志,用于检测异常签发。
存储
私钥是核心资产。我见过一个团队把私钥和证书打包进 Docker 镜像——如果你这么干,等于把保险柜密码贴在保险柜门上。正确的做法:
- 使用 HSM(硬件安全模块)或云 KMS 管理私钥。
- 如果必须存文件,用
chmod 400并放入加密分区。 - 证书链(CA+中间+叶子)可以公开,但私钥必须隔离。
轮换
证书轮换不能等到过期前最后一秒。我建议设置两个阈值:
另外,吊销列表(CRL)或OCSP stapling也要在日志里追踪,否则你根本不知道哪些证书已经被废弃。
2.4 令牌 —— 短命的临时凭证
令牌()包括JWT、OAuth 2.0 Access 、Session 等。它们的特点是时效短、可自验证、无需查数据库。我在一个微服务项目中,发现每个服务都自己解析JWT,但日志里从来没有记录过JWT的 jti(唯一标识),导致泄漏后无法精准回溯。后来我把jti强制打入日志结构,效果立竿见影。
创建
- JWT 一定要使用 RS256 或 ES256 签名,别用
none算法(有人中过这招)。 - 每个令牌必须包含唯一ID(jti),用于在黑名单或日志中匹配。
- 访问令牌(Access )有效期建议15分钟以内;刷新令牌(Refresh )可以7天,但要绑定设备指纹。
存储
前端存令牌是个老大难。我的建议:
- Web应用:用 HttpOnly + Secure + SameSite=Strict 的Cookie,别用 localStorage。
- 移动端:用系统密钥链(Keychain / Keystore),别自己写加密。
- 后端:内存或缓存(Redis),但不能写进数据库日志表里。我在审计时见过一张审计表明文存着Access ……直接导致合规失败。
轮换
令牌轮换本质上就是“刷新”。OAuth 2.0 的 Refresh Rotation 模式值得推广:每次刷新后旧刷新令牌立即作废,并颁发新刷新令牌。你在日志里可以追踪 _type 和 refresh_sequence 来发现异常重放。
2.5 一张图看透凭据生命周期
下面这张 SVG 图梳理了四种凭据从出生到消亡的全过程。我把它叫做“凭据生命线”,每次做架构评审时都会贴出来。
2.6 生命周期在日志追踪中的映射
你想想看,凭据从创建到销毁,每一步都会产生日志。如果日志里没有记录凭据的“指纹”和“事件类型”,那我们根本没法复盘。下面这张表是我推荐的最小化日志字段:
| 生命周期阶段 | 记录字段 | 作用 |
|---|---|---|
| 创建 | 凭据ID、类型、算法版本、创建者、时间 | 定位谁在什么时候发了一个什么凭据 |
| 存储 | 存储位置摘要(如Vault路径)、访问策略 | 审计凭据是否越权访问 |
| 使用(认证) | 凭据指纹、客户端IP、时间、结果 | 发现异常调用行为(如来自异地IP) |
| 轮换 | 旧指纹、新指纹、轮换原因、操作人 | 建立凭据血缘关系,避免“幽灵凭据” |
| 吊销/过期 | 凭据指纹、吊销原因、时间 | 确认凭据已失效,避免重放 |
2.7 实战避坑:凭据轮换中的常见翻车
- 新旧并行期没处理好:我曾经让新密钥和旧密钥并行24小时,结果旧密钥被先一步撤销,导致正在处理中的请求全部失败。正确的做法是先签发新密钥,确认所有消费方都切换到新密钥后,再逐步降温旧密钥(例如保留只读访问)。
- 自动化脚本忘了记录日志:用crontab轮换证书,轮换成功了没报错,但也没写日志。三个月后查出来证书早就换了,但审计人员拿着日志说“没记录,等于没做”。所以我建议:轮换脚本的第一行和最后一行都写结构化日志,中间每一步都有trace。
- 令牌刷新没考虑并发:多个进程同时刷新同一个Refresh ,导致旧令牌被撤销后,新令牌重复颁发。解决方案:加分布式锁,或者在日志里加
refresh_nonce字段来检测冲突。
嗯,以上就是四种凭据的生命周期要点。你把这些规则记牢了,再去看日志里的凭据事件,条理会清晰很多。下一层我们要聊的是如何把不同的凭据事件统一成一套日志格式——那就是“结构化日志模板”登场的时候了。