笔记 public-api-rate-limit-owner-token

公开接口防刷:限流、令牌与「我自己不限次」

2026-10-17 约 2 分钟 安全运营

公开的问答接口有个矛盾:访客要限额,站主要不限次,但两者打的是同一个端点。

默认口径:每 IP 每分钟 6 次

限流放在服务端(PublicAskRateLimiter),按 IP 记令牌。超了返回 429,并且带 Retry-After——这样前端可以老老实实告诉你"还有 N 秒"。

为什么是 6 次/分钟:一次问答的成本主要在模型调用和检索,正常人读完一段回答再问下一句,间隔通常远超 10 秒;6 次/分钟已经足够宽松,同时把"脚本连着刷"挡住了。

站主不限次:用同一个端点 + 一个头

一开始的想法是走内部端点 /api/user/ask,试过之后否掉了,两个原因:

  1. 跨域没开:CORS 拦截器只挂在 /api/public/** 上,/api/user/** 直接调会被浏览器拦;
  2. 响应结构不同:内部端点返回的字段和公开面不一样,展示层要为两条链路分叉。

所以最后走的是"同一个端点 + 一个请求头":

POST /api/public/ask
X-User-Token: <登录时拿到的 token>

后端认到有效令牌就跳过限流,响应头里回 X-Owner-Ask: 1、X-RateLimit-Limit: owner-unlimited。访客看不到这个头,也不需要知道它存在。

这不是把门打开

关键点:令牌校验复用的是登录服务的同一套校验,不是前端传什么就认什么。

  • 令牌存在浏览器 localStorage,只发给 API 域名(meta[name=wb-api] 同源才发);
  • 前端只在登录态才带这个头;
  • 后端校验失败 → 走原来的限流,不会因为"请求里有个头"就放行。

前端拿到 401/403 怎么办

清掉本地登录态,退回匿名,并把限额提示切回"每 IP 6 次/分钟"。这一步不做的话,会出现"显示不限次、实际一直被 429"的鬼故事。

CORS 只多一个头

白名单里追加 X-User-Token,别的没动:content-type 之外的自定义头一律不放。跨域放行的范围越小,出错的半径也越小。

自检口径

# 访客连打 8 次(1 分钟内)
=> 期望:前 6 次 200,后 2 次 429 且带 Retry-After

# 登录态连打 20 次
=> 期望:全部 200,响应头有 X-Owner-Ask: 1

# 伪造一个 token
=> 期望:按访客口径限流(不是 200)

第三条是最容易被忽略的——「有个头就放行」这种实现,第一次自测就能过,然后一直错下去。

← 回到笔记列表