安全与风控
在 Fastify 中实现或排查 OAuth…
在 Fastify 应用里搭建或调试 OAuth 2.0/2.1 登录、PKCE、访问令牌校验、刷新令牌轮换和受保护路由时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 Fastify 中实现或排查 OAu特定
解决思路
- 安装依赖:运行 `npm install @fastify/oauth2 @fastify/cookie @fastify/session fastify-plugin`。
- 在 `plugins/oauth.ts` 中通过 `fastify-plugin` 注册 `@fastify/oauth2`,配置 `name`、`scope`(如 `['openid','profile','email']`)、`credentials.client.id`/`secret`、`auth.authorizeHost`/`authorizePath`/`tokenHost`/`tokenPath`、`startRedirectPath`、`callbackUri`、`pkce: 'S256'`、`generateStateFunction` 和 `checkStateFunction`。
- 把 `pkce` 设为 `'S256'`,对所有公共客户端使用 PKCE(RFC 7636)。
- 在 `generateStateFunction` 中生成并保存 state:`req.session.state = crypto.randomUUID()`。
- 在 `checkStateFunction` 中比较 `req.query.state === req.session.state`,不匹配时回调 `new Error('State mismatch')`。
- 在继续前确认 `callbackUri` 与授权服务器注册的 redirect URI 完全匹配(RFC 6749 §3.1.2)。
- 在 `routes/auth.ts` 的 `/login/callback` 路由中调用 `fastify.oauth2.getAccessTokenFromAuthorizationCodeFlow(request)`,由 `@fastify/oauth2` 自动验证 state 并交换 code。
- 只存储需要的令牌,绝不记录原始 token;用 `request.session.set('accessToken', ...)` 和 `request.session.set('refreshToken', ...)` 保存后重定向到 `/`。
- 提供 `/logout` 路由,调用 `request.session.destroy()` 后重定向到 `/`。
- 在 `hooks/verifyToken.ts` 中实现 `verifyToken` 中间件,先调用 `request.jwtVerify()`。
- 在 `verifyToken` 中校验必需 claims:`exp`、`iss`、`aud`、`sub`;`exp` 小于当前秒数返回 401 `token_expired`,`iss` 不等于 `EXPECTED_ISSUER` 返回 401 `invalid_issuer`,`aud` 不等于 `EXPECTED_AUDIENCE` 返回 401 `invalid_audience`,异常返回 401 `invalid_token` 和 `error_description`。
- 每个请求都验证 `exp`、`iss`、`aud`、`sub`,不要跳过(RFC 7519 §4)。
- 第三方服务器签发的 token 使用非对称算法 `RS256`/`ES256` 的 `fastify.jwt.verify` 校验,不要用 `HS256`。
- 在 `routes/api.ts` 中用 `fastify.addHook('onRequest', verifyToken)` 保护该作用域内所有路由。
- 为受保护路由(如 `/me`)定义响应 schema,返回 `{ sub: user.sub }`。
- 实现刷新令牌轮换:用 `fastify.oauth2.getNewAccessTokenUsingRefreshTokenFlow({ refresh_token: refreshToken })` 获取新令牌。
- 若使用轮换,每次使用后总是替换存储的 refresh token,返回 `newToken.token.refresh_token ?? refreshToken`(RFC 6749 §10.4)。
- 按允许列表校验 redirect URI(RFC 6749 §3.1.2)。
- 对所有公共客户端强制使用 PKCE `S256`(RFC 7636 §4.2)。
- 校验 `state` 防止 CSRF(RFC 6749 §10.12)。
- 在每个 JWT 上校验 `iss`、`aud`、`exp`(RFC 7519 §4)。
- 每次使用 refresh token 都轮换(RFC 6749 §10.4)。
- 全程使用 HTTPS;拒绝 HTTP redirect URI(RFC 6749 §3.1.2.1)。
- 对 token endpoints 做限流(OAuth 2.1 §7)。
- 不要把 token 存到 localStorage;改用 `HttpOnly`、`Secure`、`SameSite=Strict` cookies。
- 不要跳过 audience 校验;否则 token 可跨服务复用。
- 不要使用 implicit flow;OAuth 2.1 已弃用,改用 authorization code + PKCE。
- 不要在浏览器应用中接受 `response_type=token`;URL fragment 中的 token 会泄漏到日志/referrer。
- 不要对第三方 token 使用对称签名 `HS256`;使用 `RS256`/`ES256` 和 JWKS endpoint。
- 设备授权流参考 `DEVICE_FLOW.md`(RFC 8628)。
- JWKS 轮换、缓存策略和不透明 token introspection 参考 `TOKEN_VALIDATION.md`。
- 机器到机器服务认证参考 `CLIENT_CREDENTIALS.md`。
- 原生/移动应用流程和自定义 URI scheme 参考 `MOBILE_OAUTH.md`(RFC 8252)。
适用边界 · 注意事项
- 不是 Fastify 应用、或不使用 @fastify/oauth2 等插件时,不能直接套用这些步骤。
- 无法使用 HTTPS、无法注册精确 callback URI 或无法控制授权服务器配置时,不应实施浏览器 OAuth 流程。
- 不需要 OAuth 的本地/离线认证场景,不应引入完整 OAuth 流程。