跳到主要内容

安全指南

加密算法

密码哈希

CaelLabID 使用 Argon2ID 作为密码哈希算法:

$argon2id$v=19$m=65536,t=3,p=4$salt$hash
参数说明
内存成本65536 KiB抵御 GPU 攻击
时间成本3 次迭代平衡安全性与性能
并行度4 线程利用多核 CPU

兼容性:系统自动兼容旧版 bcrypt 哈希,用户下次修改密码时自动升级为 Argon2ID。

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 的建议:

方式安全性说明
服务端 SessionToken 存在服务端 Session 中,浏览器只有 Session ID
HttpOnly CookieCookie 设置 HttpOnly + Secure + SameSite
内存变量SPA 应用存 JS 变量中,页面刷新后丢失
localStorage容易被 XSS 攻击窃取
sessionStorage同上

不要将 Token 存储在:

  • 浏览器 localStorage/sessionStorage(XSS 风险)
  • URL 查询参数中
  • 日志文件中
  • 前端代码的变量中(可被查看源码获取)

Client Secret 安全

  • Client Secret 仅在创建时显示一次
  • 只在服务端代码中使用
  • 不要提交到 Git 仓库
  • 不要在前端 JavaScript 中使用
  • 不要在日志中打印
  • 定期轮换(建议每 3-6 个月)

Refresh Token 安全

  • Refresh Token 有效期 30 天
  • 每次使用都会轮换(旧的失效,返回新的)
  • 重置 Client Secret 会撤销所有活跃的 Refresh Token
  • 存储建议与 Access Token 相同

CSRF 防护

state 参数

在授权请求中始终使用 state 参数:

// 1. 生成随机 state
$state = bin2hex(random_bytes(16));

// 2. 存储到 Session
$_SESSION['oauth_state'] = $state;

// 3. 授权 URL 带上 state
$url = "https://id.caellab.com/oauth/authorize?..." . "&state={$state}";

// 4. 回调时验证
if ($_GET['state'] !== $_SESSION['oauth_state']) {
die('CSRF 检测失败');
}

CSRF Token

所有 POST 表单(授权确认、登录、注册等)都要求 _csrf 参数。CaelLabID 的授权页面会自动包含 CSRF Token。

推荐实践

1. 最小权限

只申请你需要的 Scope:

# 仅获取用户标识
scope=openid

# 获取用户标识 + 用户名头像
scope=openid profile
信息

profile 属于中危权限,用户在授权页面会看到确认提示,可以自由决定是否授予。

2. Token 过期处理

Access Token 有效期 1 小时。你的应用应该:

async function apiCall(url) {
let res = await fetch(url, {
headers: { Authorization: `Bearer ${accessToken}` }
});

// Token 过期,自动刷新
if (res.status === 401) {
const newTokens = await refreshAccessToken(refreshToken);
if (newTokens) {
saveTokens(newTokens);
res = await fetch(url, {
headers: { Authorization: `Bearer ${newTokens.access_token}` }
});
} else {
// Refresh Token 也过期了,需要重新授权
redirectToAuthPage();
}
}

return res.json();
}

3. 退出登录

用户退出时,撤销 Token:

// 撤销 Access Token
curl -X POST https://id.caellab.com/oauth/revoke \
-d "token=" . $accessToken;

// 清除本地 Session
session_destroy();

4. 安全退出流程

1. 调用 /oauth/revoke 撤销 Token
2. 清除本地 Session/Cookie
3. 可选:重定向到 CaelLabID 的 /logout

频率限制

所有 API 端点都有频率限制,超限会返回 HTTP 429。频率限制基于请求方 IP(用户/客户端的 IP 地址),不是服务器级别的全局限制。

端点限制维度
/oauth/token30 次/分钟请求方 IP
/api/userinfo60 次/分钟请求方 IP
/api/token-info30 次/分钟请求方 IP
登录5 次/分钟请求方 IP
注册3 次/小时请求方 IP
创建应用1 次/分钟开发者
重置密钥3 次/小时开发者

被限流时,响应头包含 Retry-After 字段,告诉你多少秒后可以重试。

常见攻击防护

攻击防护措施
CSRFstate 参数 + CSRF Token
XSSHttpOnly Cookie + 服务端渲染
暴力破解IP 频率限制(5 次/分钟)
授权码枚举授权码 64 字符 hex + 10 分钟过期 + 一次性使用
Token 泄露SHA-512 哈希存储 + 过期机制 + 撤销机制
重放攻击授权码一次性使用 + Refresh Token 轮换
跨应用关联应用级 subject_id