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,并启用文件系统加密。

避坑指南:我曾经见过一个漂亮的Python脚本,用 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 镜像——如果你这么干,等于把保险柜密码贴在保险柜门上。正确的做法:

  1. 使用 HSM(硬件安全模块)或云 KMS 管理私钥。
  2. 如果必须存文件,用 chmod 400 并放入加密分区。
  3. 证书链(CA+中间+叶子)可以公开,但私钥必须隔离。

轮换

证书轮换不能等到过期前最后一秒。我建议设置两个阈值:

警告:证书过期前30天发出Notice(Info级别日志),过期前7天未轮换则升级为Error告警。对内部mTLS证书,最好用ACME协议自动化轮换(如 cert-manager)。

另外,吊销列表(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 图梳理了四种凭据从出生到消亡的全过程。我把它叫做“凭据生命线”,每次做架构评审时都会贴出来。

凭据生命线 —— 四种凭据的创建 → 存储 → 轮换 密码 存储:bcrypt + 盐 轮换:泄漏时强制 存储:Vault / KMS 轮换:90天 / 泄漏立即 证书 存储:HSM / 文件加密 轮换:ACME自动化 令牌 存储:HttpOnly Cookie 轮换:15分钟刷新 销毁 / 吊销 / 过期 → 日志标记 日志追踪要点 • 每次创建/轮换/吊销都输出结构化日志(含凭据指纹、操作人、源IP) • 轮换时要同时记录旧凭据指纹和新凭据指纹,形成血缘链 • 过期前/后的事件要按“预过期-过期-已吊销”三个等级分级告警

2.6 生命周期在日志追踪中的映射

你想想看,凭据从创建到销毁,每一步都会产生日志。如果日志里没有记录凭据的“指纹”和“事件类型”,那我们根本没法复盘。下面这张表是我推荐的最小化日志字段:

生命周期阶段记录字段作用
创建凭据ID、类型、算法版本、创建者、时间定位谁在什么时候发了一个什么凭据
存储存储位置摘要(如Vault路径)、访问策略审计凭据是否越权访问
使用(认证)凭据指纹、客户端IP、时间、结果发现异常调用行为(如来自异地IP)
轮换旧指纹、新指纹、轮换原因、操作人建立凭据血缘关系,避免“幽灵凭据”
吊销/过期凭据指纹、吊销原因、时间确认凭据已失效,避免重放
一个小习惯:我每次上线新服务,都会先检查这个服务的凭据日志覆盖率。标准是:凭据从生成到销毁100%可追溯。如果缺某个阶段,立刻打补丁。别等出了事再补日志——那时数据已经丢了。

2.7 实战避坑:凭据轮换中的常见翻车

  • 新旧并行期没处理好:我曾经让新密钥和旧密钥并行24小时,结果旧密钥被先一步撤销,导致正在处理中的请求全部失败。正确的做法是先签发新密钥,确认所有消费方都切换到新密钥后,再逐步降温旧密钥(例如保留只读访问)。
  • 自动化脚本忘了记录日志:用crontab轮换证书,轮换成功了没报错,但也没写日志。三个月后查出来证书早就换了,但审计人员拿着日志说“没记录,等于没做”。所以我建议:轮换脚本的第一行和最后一行都写结构化日志,中间每一步都有trace。
  • 令牌刷新没考虑并发:多个进程同时刷新同一个Refresh ,导致旧令牌被撤销后,新令牌重复颁发。解决方案:加分布式锁,或者在日志里加 refresh_nonce 字段来检测冲突。

嗯,以上就是四种凭据的生命周期要点。你把这些规则记牢了,再去看日志里的凭据事件,条理会清晰很多。下一层我们要聊的是如何把不同的凭据事件统一成一套日志格式——那就是“结构化日志模板”登场的时候了。