18.10-IP级限流
要点
- IP 级限流是防滥用的第一道防线——不需要登录就能生效,挡住匿名攻击
- Cloudflare 自动加
CF-Connecting-IP头——这是最可靠的客户端 IP 来源,不要信任X-Forwarded-For - IPv6 限流要小心——同一用户可能用多个 IPv6 地址,按 /48 子网限流更合理
- 实现:KV 做高速计数,D1 做持久化黑名单
- 常见绕过手法:代理池、IPv6 轮换、Header 伪造——应对策略不同
- IP 限流通常跟用户级限流叠加——匿名请求按 IP,登录请求按 user_id
1. 为什么需要 IP 限流
用户级限流需要登录才能生效。但很多接口是公开的:
- 注册/登录接口——防止撞库攻击
- 密码重置——防止邮件轰炸
- 公开查询接口——防止被爬虫刷爆
- 上传接口——防止恶意大文件上传
这些接口需要 IP 级限流——不依赖登录状态,任何请求都能限制。
2. 获取真实 IP
2.1 Cloudflare 的 CF-Connecting-IP
请求经过 Cloudflare 后,Cloudflare 会在请求头里加 CF-Connecting-IP——这是客户端的真实 IP(Cloudflare 从 TCP 连接拿的,无法伪造):
app.get('/api/public', (c) => {
const clientIP = c.req.header('CF-Connecting-IP')
// 这是真实 IP,Cloudflare 保证准确
return c.json({ yourIp: clientIP })
})2.2 不要信任 X-Forwarded-For
X-Forwarded-For 是客户端可以随便改的 header:
curl -H 'X-Forwarded-For: 1.2.3.4' https://api.example/如果你从 X-Forwarded-For 取 IP,攻击者可以随便指定 IP 绕过限流。
CF-Connecting-IP 是 Cloudflare 根据 TCP 连接加的,客户端无法伪造。
2.3 不经过 Cloudflare 的情况
如果 Worker 不经过 Cloudflare 代理(直接暴露 IP),CF-Connecting-IP 不存在。此时从 TCP 连接拿 IP——但 Worker 环境没有直接暴露这个信息的 API。
一般建议:Worker 必须走 Cloudflare 代理,既保证安全又能拿到真实 IP。
3. IP 限流实现
3.1 Hono 中间件
// src/middleware/ip-rate-limit.ts
import { Context, Next } from 'hono'
type IPRateLimitConfig = {
limit: number
windowSeconds: number
keyFn?: (ip: string) => string // 自定义 key 生成(比如按子网)
}
export function ipRateLimit(config: IPRateLimitConfig) {
return async (c: Context, next: Next) => {
const clientIP = c.req.header('CF-Connecting-IP')
if (!clientIP) {
// 没有 Cloudflare IP 头,可能是绕过代理的直接访问
return c.json({ error: 'Access denied' }, 403)
}
const key = config.keyFn ? config.keyFn(clientIP) : `ip:${clientIP}`
const result = await checkIPLimit(c.env, key, config)
if (!result.allowed) {
c.header('Retry-After', String(config.windowSeconds))
c.header('X-RateLimit-Limit', String(config.limit))
c.header('X-RateLimit-Remaining', '0')
return c.json({ error: 'Too many requests' }, 429)
}
c.header('X-RateLimit-Limit', String(config.limit))
c.header('X-RateLimit-Remaining', String(result.remaining))
await next()
}
}
async function checkIPLimit(
env: { RATE_LIMIT_KV: KVNamespace },
key: string,
config: IPRateLimitConfig
) {
const window = Math.floor(Date.now() / 1000 / config.windowSeconds)
const windowKey = `${key}:${window}`
const current = parseInt((await env.RATE_LIMIT_KV.get(windowKey)) || '0')
if (current >= config.limit) {
return { allowed: false, remaining: 0 }
}
await env.RATE_LIMIT_KV.put(windowKey, String(current + 1), {
expirationTtl: config.windowSeconds * 2,
})
return { allowed: true, remaining: config.limit - current - 1 }
}3.2 使用
// 公开接口:每 IP 每分钟 20 次
app.use('/api/public/*', ipRateLimit({
limit: 20,
windowSeconds: 60,
}))
// 注册接口:每 IP 每小时 5 次
app.use('/api/auth/register', ipRateLimit({
limit: 5,
windowSeconds: 3600,
}))
// 密码重置:每 IP 每天 3 次
app.use('/api/auth/reset-password', ipRateLimit({
limit: 3,
windowSeconds: 86400,
}))4. IPv6 限流的问题
IPv6 地址空间巨大,ISP 通常给每个用户分配一个 /48 或 /56 子网。同一用户每次连接可能用不同的 IPv6 地址:
2001:db8:1234:5678::1
2001:db8:1234:5678::2
2001:db8:1234:5678::3
如果按完整 IPv6 地址限流,攻击者可以用不同地址绕过。
4.1 按 IPv6 子网限流
function normalizeIPv6(ip: string): string {
// IPv4 直接返回
if (!ip.includes(':')) return ip
// IPv6 按 /48 子网限流(前 3 段)
const parts = ip.split(':')
return parts.slice(0, 3).join(':') + '::/48'
}
// 使用
app.use('/api/public/*', ipRateLimit({
limit: 20,
windowSeconds: 60,
keyFn: (ip) => `ip:${normalizeIPv6(ip)}`,
}))4.2 IPv4 和 IPv6 统一处理
function ipToLimitKey(ip: string): string {
if (ip.includes(':')) {
// IPv6:按 /48
const parts = ip.split(':')
return parts.slice(0, 3).join(':')
} else {
// IPv4:完整地址
return ip
}
}5. 绕过与防御
5.1 代理池
攻击者用代理池,每次请求用不同 IP。单 IP 限流完全无效。
防御:
- 识别数据中心 IP 段(proxylists、IPinfo 数据库)
- 数据中心 IP 用更严格的限制
- Cloudflare Bot Fight Mode 自动拦截已知代理
async function isDatacenterIP(ip: string): Promise<boolean> {
// 查 IPinfo 或本地数据库
const record = await env.IP_DB.get(`ip-info:${ip}`)
if (!record) return false
const info = JSON.parse(record)
return info.type === 'hosting' || info.type === 'vpn'
}
// 数据中心 IP 限流更严格
app.use('/api/public/*', async (c, next) => {
const ip = c.req.header('CF-Connecting-IP')!
const limit = await isDatacenterIP(ip) ? 5 : 20
return ipRateLimit({ limit, windowSeconds: 60 })(c, next)
})5.2 Header 伪造
攻击者伪造 X-Forwarded-For 绕过 IP 限流。
防御:只用 CF-Connecting-IP,不读 X-Forwarded-For。
5.3 IPv6 轮换
攻击者用同一 /48 下的不同 IPv6 地址。
防御:按 /48 子网限流(前面已讲)。
5.4 慢速攻击(Slowloris)
攻击者用极慢的速率发请求,不触发限流但持续占用连接。
防御:Cloudflare 自动处理大部分慢速攻击。Worker 本身是无状态的,不怕慢速连接。
6. IP 黑名单
某些 IP 行为恶劣(频繁攻击、爬虫),直接加黑名单拒绝所有请求:
// src/middleware/ip-blacklist.ts
export async function ipBlacklist(c: Context, next: Next) {
const ip = c.req.header('CF-Connecting-IP')
if (!ip) return c.json({ error: 'Access denied' }, 403)
const blocked = await c.env.BLACKLIST_KV.get(`blocked:${ip}`)
if (blocked) {
return c.json({ error: 'Access denied' }, 403)
}
await next()
}
// 加黑名单(管理员接口)
app.post('/admin/blacklist/:ip', async (c) => {
const ip = c.req.param('ip')
const reason = (await c.req.json()).reason || 'abuse'
await c.env.BLACKLIST_KV.put(`blocked:${ip}`, reason, {
expirationTtl: 86400 * 7, // 7 天后自动解除
})
return c.json({ ok: true })
})6.1 自动加黑名单
根据限流触发频率自动加黑名单:
// 每次限流触发时记录
async function recordRateLimitHit(env: Env, ip: string) {
const key = `ratelimit-hits:${ip}:${Math.floor(Date.now() / 86400)}`
const count = parseInt((await env.RATE_LIMIT_KV.get(key)) || '0') + 1
await env.RATE_LIMIT_KV.put(key, String(count), { expirationTtl: 86400 * 2 })
// 一天内触发 100 次以上,自动加黑名单
if (count >= 100) {
await env.BLACKLIST_KV.put(`blocked:${ip}`, 'auto:blocked', {
expirationTtl: 86400 * 7,
})
}
}7. IP 限流 vs 用户限流
| 维度 | IP 限流 | 用户限流 |
|---|---|---|
| 生效条件 | 无需登录 | 需要登录 |
| 准确度 | 中(NAT、代理会影响) | 高(user_id 唯一) |
| 适用接口 | 公开接口(注册、登录、公开查询) | 需要鉴权的接口 |
| 绕过难度 | 中(代理池可绕过) | 低(要注册多个账号) |
| 成本 | 低(KV 计数) | 中(D1 查配额) |
实际部署通常两者叠加:
// 公开接口:IP 限流
app.use('/api/auth/*', ipRateLimit({ limit: 20, windowSeconds: 60 }))
// 登录后的接口:用户限流 + IP 限流
app.use('/api/ai/*', ipRateLimit({ limit: 100, windowSeconds: 60 }))
app.use('/api/ai/*', userRateLimit) // 按 user_id 限两层都通过才放行——匿名攻击被 IP 限流挡住,登录后的滥用被用户限流限制。
8. Cloudflare 的额外保护
Cloudflare 自带一些防护能力,不需要在 Worker 里重复实现:
- Bot Fight Mode:自动识别和拦截机器人
- WAF:Web 应用防火墙,拦截常见攻击模式
- DDoS 保护:大规模流量攻击自动吸收
- Rate Limiting Rules:Cloudflare Dashboard 里配置限流规则,在边缘就拦截,根本不到 Worker
Cloudflare Rate Limiting Rules 可以在 Dashboard → Security → WAF → Rate limiting rules 配置。好处是限流在边缘执行,节省 Worker 计算资源。
请求 → Cloudflare Rate Limit(边缘)→ Worker Rate Limit(应用层)→ 业务
边缘限流做粗粒度(每 IP 每分钟 100 次),应用层做细粒度(每用户每天配额)。
9. 小结
IP 级限流的核心:
- 真实 IP:用
CF-Connecting-IP,不要信任X-Forwarded-For - IPv6:按 /48 子网限流,避免同一用户被多 IP 绕过
- 实现:KV 做高速计数,固定窗口或滑动窗口算法
- 绕过防御:代理池用数据中心 IP 识别,Header 伪造靠只用
CF-Connecting-IP,IPv6 轮换靠子网限流 - 黑名单:根据限流触发频率自动加黑名单
- 与用户限流叠加:公开接口按 IP,登录接口按 user_id,两层都过才放行
- Cloudflare 边缘限流:粗粒度在边缘,细粒度在应用层
下一篇讲模型调用限流——按 token 数/请求数限流、分级限流(按 model)、成本预算控制。