Repo .git was damaged (refs/ missing, index truncated, object store near-empty). Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak, then full worktree committed as a rollback baseline.
3.4 KiB
3.4 KiB
共享出口匿名投票防刷修改与测试报告
- 时间:2026-08-26
- 目标:解决 Cloudflare +
trust_proxy=true场景下,局域网用户共享公网 IP 导致同一投票只能一人提交的问题。 - 策略:签名
vote_fp负责设备级一人一票;IP 仅用于高阈值突发风控,不再承担同一投票的一票封顶。
修改内容
1. 删除匿名投票的 IP 一票封顶
route/vote/vote.go:
- 移除提交处理器内
CountByIP(... 24h)且count >= 1即拒绝的逻辑。 - 匿名用户继续通过有效 HMAC
vote_fp与数据库唯一索引防止同一浏览器/设备重复投票。 - 无效或缺失 Cookie 仍会被重新签发,不会成为可信身份。
2. 新增投票提交专用的身份优先限流
internal/route/ratelimit.go:
新增 voteSubmitRateLimitMiddleware,投票提交同时经过两层限制:
- 登录用户:按
user:<uid>独立限流,额度为常规参数的 3 倍。 - 匿名用户:按有效
token:<vote_fp>独立限流。 - 所有匿名请求:同时经过
ip:<client-ip>高阈值兜底限流,默认是传入基础额度的 10 倍。 - 无效/缺失 Cookie 不会获得独立 token 限流身份,只能使用 IP 兜底。
- 触发限制时返回 HTTP 429,不进入实际投票提交处理器。
internal/route/route.go 已将 /vote/{token}/submit 从普通 IP 限流切换到该专用中间件。
3. 校准 Cloudflare 客户端 IP 解析
internal/shared/ip.go:
TrustProxy=true时优先读取 Cloudflare 官方CF-Connecting-IP。- 若该头不存在或非法,再回退到
X-Real-IP、X-Forwarded-For,最后使用RemoteAddr。 TrustProxy=false时仍完全忽略这些请求头,只使用RemoteAddr。- 新增 Cloudflare 头优先级和不信任代理时忽略头部的测试。
4. 更新设计文档
anonymous-vote-design.md 已同步新的防刷矩阵、token 流程和共享出口说明。
验证结果
通过:
gofmt -d internal/route/ratelimit.go internal/route/route.go \
internal/shared/ip.go route/vote/vote.go
# 无输出
go test -count=1 ./internal/shared ./route/vote ./internal/template \
./templates ./internal/i18n ./internal/cache ./internal/security/autoban
# 全部 PASS
go vet ./route/vote ./internal/shared ./internal/template ./templates ./internal/i18n
# 无输出
补充测试覆盖:
- 同一共享 IP 下多个不同 token 彼此独立;
- 同一 token 仍受独立限流;
- IP 兜底允许多次正常请求,但能阻断异常突发;
- Cloudflare
CF-Connecting-IP优先于其他代理头; - 未启用可信代理时伪造的代理头不会生效。
go test ./internal/route 当前仍无法编译完成,原因是项目既有的 github.com/chai2010/[email protected] 在本机缺少 CGO/WebP 编译符号,并非本次限流逻辑测试失败。
Cloudflare 部署注意事项
- 源站防火墙应只允许 Cloudflare 官方 IP 段访问源站端口,否则攻击者可能直接访问源站并伪造
CF-Connecting-IP。 - 保持
trust_proxy=true的前提是源站确实位于可信反向代理之后。 - 重要投票建议开启 Turnstile 或要求登录;匿名签名 Cookie 只能保证设备级防重,无法阻止清 Cookie、换浏览器或自动化客户端。
- 当前共享 IP 兜底阈值为 200 次/分钟(投票路由传入基础值 20),可根据真实并发量调整。