跳到主要内容

安全要求

本页所有内容都是对你的对接的要求。没有一条是系统替你强制执行的。

保护好 client secret​

Client secret 等同于你应用的身份。任何拿到它的人都能换取签发给你的授权码,也能以你的 名义刷新令牌。

  • 放在服务器上。永远不要打进浏览器产物、移动端二进制、公开仓库或客户端配置文件。
  • 从密钥管理服务或环境变量加载,不要写进源码。
  • 怀疑泄露就在开发者页面重新生成。先部署新值 —— 重新生成立即生效,任何还持有旧值 的进程会立刻开始收到 invalid_client。

如果你的应用无法保存密钥,请改用 PKCE,并接受客户端类型里 列出的限制。

每次都校验 state​

每次发起授权都生成一个随机 state,绑定到用户会话,回调时 state 对不上就拒绝。

不这么做,攻击者可以用自己的 Nanako 账号走完一次授权,再把得到的回调 URL 交给你的 用户,从而把受害者的会话关联到攻击者的身份上。

取值要保持 URL 安全 —— 十六进制或无填充 base64url。它会被原样拼进回调 URL,不做转义, 所以 &、#、= 和空格都会破坏这次跳转。

使用 PKCE​

PKCE 的成本是一次哈希,收益是关掉了"授权码泄露后被别人换走"的窗口 —— 泄露渠道可能是 代理日志、浏览器历史或 Referer 头。

请发送 code_challenge_method=S256,必须是这个精确写法;s256 会静默回落到 plain, 不提供任何保护。

机密客户端同样受益。但要注意:如果你同时用了 PKCE 和 client secret,带 challenge 签发 的授权码是由 challenge 来校验的,所以请像保管密钥一样保管 verifier。

注册精确的回调地址​

回调地址逐字节比对,这正是你想要的行为。不要试图绕开它:

  • 面向用户的地址一律用 https://。http:// 在注册时被接受是为了本地开发 —— 永远不要注册一个公网可达的明文地址。
  • 每个真实回调注册一个地址。不要注册一个宽泛的地址再在内部转发。
  • 每次请求的附带数据放在 state 里,不要放进回调地址。带查询字符串的回调地址会让整个 回调失效。

把令牌当作凭证处理​

  • 永远不要打日志。 access token 和 refresh token 都是 bearer 凭证,持有即可冒充用户。
  • 加密存储,并按所属用户隔离。
  • 不要放进 URL。 access token 走 Authorization 头。
  • 不要解析。 它们是不透明字符串,里面没有可读结构。

Access token 有效期两小时。Refresh token 自首次授权起 30 天,且不会因使用而延长 —— 请为"整条链结束、用户需要重新授权"这件事做好准备。

串行化刷新​

Refresh token 每次使用都会轮换,而提交一个已经轮换过的 token 会被判定为重放:该用户在 该应用下的所有令牌会被撤销,用户被踢出你的应用。

两条规则可以避免:

  1. 先把新的 refresh token 持久化,再用响应里的其他内容。收到与写入之间崩溃一次, 代价就是用户的会话。
  2. 每个用户同时只允许一次刷新。 加锁,或者由单一的定时 worker 主动提前刷新,而不是 在每个请求处理器里被动刷新。

校验实际授予的 scope​

token 响应里的 scope 是用户真正批准的范围。请读取它,并据此约束你自己的行为。不要 假设请求发出去是什么就被授予了什么。

尊重撤销​

用户可以随时在 Nanako 账号里撤回你的应用。这件事没有回调通知 —— 你会在下一次刷新收到 invalid_grant 时才知道。

把它当作"授权已收回":删除已存的令牌和缓存的用户资料,重新展示登录入口。不要重试,也 不要继续用缓存数据服务这个用户。

当你自己的应用让用户退出登录时,请显式撤销令牌,而不是只把它丢掉。见 第 6 步。

用 sub 建关联,不要用邮箱​

sub 是稳定标识。邮箱和手机号可以更换、释放、被别人重新使用;昵称可以随意改。以邮箱 做匹配的账号体系,等于埋了一颗账号接管的雷。

首次登录时把 sub 存为外键,之后一律按它匹配。

浏览器端应用​

第三方网页来源无法直接调用 /oauth/token 或 /oauth/userinfo。Nanako 的 CORS 白名单 由运营方配置且没有通配符,浏览器会拦掉响应。

更麻烦的是,这个拦截并不干净。token 请求用的是 CORS 安全内容类型,所以请求会真的发出 并被处理 —— 授权码在服务端已经被消费 —— 之后浏览器才丢弃你的脚本读不到的响应。也就是 每尝试一次就烧掉一个一次性授权码,失败之后无法重试。

请把换取令牌这一步放在你的服务端。如果确实必须做纯浏览器架构,动手之前请先联系我们。

Nanako 这一侧做了什么​

供你评估威胁模型参考,提供方侧的情况:

  • Access token 和 refresh token 以 SHA-256 摘要存储,数据库里没有可被重放的原值。
  • Client secret 以 bcrypt 哈希存储,创建之后无法还原。
  • Refresh token 每次使用都轮换,重放已轮换的 token 会撤销该用户在该应用下的整条链。
  • 授权码与客户端、回调地址和用户绑定,10 分钟过期,只能使用一次。
  • authorize、token、userinfo 事件都会记录时间、IP 和设备类型,用户可在账号设置里查看。
  • 每个响应都带 X-Frame-Options: DENY 和禁止内嵌的 CSP,流程无法被 iframe 套用。