安全指南
加密算法
密码哈希
CaelLabID 使用 Argon2id 作为密码哈希算法:
$argon2id$v=19$m=65536,t=3,p=4$salt$hash
| 参数 | 值 | 说明 |
|---|---|---|
| 内存成本 | 65536 KiB(64 MB) | 抵御 GPU/ASIC 攻击 |
| 时间成本 | 3 次迭代 | 平衡安全性与性能 |
| 并行度 | 4 线程 | 利用多核 CPU |
兼容性:系统自动兼容旧版 bcrypt 哈希,用户下次修改密码时自动升级为 Argon2id。
Salt(盐值)
Argon2id 内置自动生成随机盐值(128 位),每个用户每次哈希的盐值都不同,集成在哈希字符串中:
$argon2id$v=19$m=65536,t=3,p=4$VGhpcyBpcyBhIHNhbHQ$hash
- ❌ 不需要单独存储或管理 salt
- ✅ 相同密码产生完全不同的哈希值
- ✅ 防止彩虹表攻击
Pepper(胡椒)
在 Argon2id 之上,系统额外使用 Pepper 机制:
// 注册
$passwordHash = password_hash($password . $pepper, PASSWORD_ARGON2ID);
// 验证
$passwordWithPepper = $password . $pepper;
password_verify($passwordWithPepper, $storedHash);
Pepper 与 Salt 的区别:
| 特性 | Salt | Pepper |
|---|---|---|
| 存储位置 | 哈希字符串内 | 服务器环境变量 APP_PEPPER |
| 是否保密 | 公开 | 严格保密 |
| 生成方式 | 自动随机生成 | 运维手动设置 |
| 共享范围 | 每个用户不同 | 所有用户共用 |
- Pepper 存储在服务端配置文件
config/app.php,通过环境变量APP_PEPPER设置 - 即使数据库泄露,攻击者没有 Pepper 也无法破解密码
- Pepper 泄露后只需更改环境变量,所有密码即失效(用户需重置密码)
- 推荐使用 64 字节以上随机字符串作为 Pepper
Token 哈希
Access Token 和 Refresh Token 使用 SHA-512 哈希存储:
token_hash VARCHAR(128) -- SHA-512 哈希值
兼容性:验证时会依次尝试 SHA-512 和 SHA-256,旧的 SHA-256 记录无需迁移即可正常使用。
Client Secret 哈希
Client Secret 使用 SHA-512 哈希存储:
兼容性:验证时会依次尝试明文、SHA-256、SHA-512,旧记录完全兼容。
Token 存储安全
服务端存储
CaelLabID 在数据库中只存储 Token 的 SHA-512 哈希,不存储明文:
-- oauth_access_tokens 表
token_hash VARCHAR(128) -- SHA-512 哈希,而非明文 token
即使数据库泄露,攻击者也无法还原出原始 Token。
客户端存储
在你的应用中存储 Token 的建议:
| 方式 | 安全性 | 说明 |
|---|---|---|
| 服务端 Session | 高 | Token 存在服务端 Session 中,浏览器只有 Session ID |
| HttpOnly Cookie | 高 | Cookie 设置 HttpOnly + Secure + SameSite |
| 内存变量 | 中 | SPA 应用存 JS 变量中,页面刷新后丢失 |
| localStorage | 低 | 容易被 XSS 攻击窃取 |
| sessionStorage | 低 | 同上 |
不要将 Token 存储在:
- 浏览器 localStorage/sessionStorage(XSS 风险)
- URL 查询参数中
- 日志文件中
- 前端代码的变量中(可被查看源码获取)
Client Secret 安全
Client Secret仅在创建时显示一次- 只在服务端代码中使用
- 不要提交到 Git 仓库
- 不要在前端 JavaScript 中使用
- 不要在日志中打印
- 定期轮换(建议每 3-6 个月)
PKCE(授权码拦截防护)
授权码模式的最后一道防线:应用在授权请求里带上 code_challenge(SHA256(code_verifier) 的 base64url 摘要,可以公开),换 Token 时带上 code_verifier(原始随机串,只存在应用自己的服务端)。服务端比对摘要,一致才发 Token,因此授权码即使在路上被截获也换不出 Token。
- 只支持
S256;plain相当于把验证值明文放在 URL 里,已被拒绝 code_verifier需 43-128 位 unreserved 字符(A-Z a-z 0-9 - . _ ~),建议每次授权重新生成、用完即弃- 公共客户端(桌面 App / SPA / CLI)强制 PKCE:它们拿不住 Client Secret,PKCE 是唯一的凭据保护
- 机密客户端可在开放平台的应用设置里勾选「强制该应用使用 PKCE」,新建应用默认勾选
- 校验失败不会消耗授权码,用户可以重新换一次
PKCE 不替代 Client Secret
机密客户端开了 PKCE 也要照常校验 Client Secret。PKCE 防的是「授权码被截获」,密钥防的是「应用身份被冒用」,两者管的事不同。
Refresh Token 安全
- Refresh Token 有效期 30 天
- 每次使用都会轮换(旧的失效,返回新的)
- 重置 Client Secret 会撤销所有活跃的 Refresh Token
- 存储建议与 Access Token 相同