rate-limiting
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRate Limiting - Preventing Brute Force & Resource Abuse
速率限制 - 防范暴力破解与资源滥用
Why Rate Limiting Matters
速率限制的重要性
The Brute Force Problem
暴力破解风险
Without rate limiting, attackers can try thousands of passwords per second. A 6-character password has 308 million possible combinations.
Without rate limiting:
- At 1,000 attempts/second → Cracked in 5 minutes
With our rate limiting (5 requests/minute):
- At 5 attempts/minute → Would take 117 years
如果没有速率限制,攻击者每秒可以尝试数千个密码。一个6位密码共有3.08亿种可能的组合。
无速率限制时:
- 每秒尝试1000次 → 5分钟即可破解
使用我们的速率限制(每分钟5次请求):
- 每分钟尝试5次 → 需要117年才能破解
Real-World Brute Force Attacks
真实暴力破解攻击案例
Zoom Credential Stuffing (2020):
Attackers made over 500,000 login attempts using stolen credentials. Proper rate limiting would have detected and blocked this within the first few hundred attempts.
WordPress Distributed Attacks (2021):
Multiple WordPress sites were targeted by distributed brute force attacks attempting millions of login combinations. Sites without rate limiting saw server costs spike as attackers consumed resources.
Zoom 凭证填塞攻击(2020年):
攻击者使用窃取的凭证发起了超过50万次登录尝试。合理的速率限制会在前几百次尝试时就检测到并阻断该攻击。
WordPress 分布式攻击(2021年):
多个WordPress站点遭到分布式暴力破解攻击,攻击者尝试了数百万种登录组合。未部署速率限制的站点由于攻击者消耗了大量资源,服务器成本大幅上涨。
The Cost of Resource Abuse
资源滥用的成本损失
Beyond security, rate limiting protects your infrastructure costs. Without it:
- Bots can spam your contact form thousands of times
- Attackers can abuse expensive operations (AI API calls, database queries)
- Your server bill skyrockets before you notice
Real Story:
One startup built a "summarize any article" AI feature without rate limiting. A malicious user scripted 10,000 requests in minutes. At AI API costs, this generated $9,600 in charges in 10 minutes. The attack ran 4 hours unnoticed—total cost over $200,000.
除了安全层面,速率限制还能控制你的基础设施成本。如果没有速率限制:
- 机器人可以向你的联系表单发送数千次垃圾提交
- 攻击者可以滥用高成本操作(AI API调用、数据库查询)
- 你可能还没察觉,服务器账单就已经暴涨
真实案例:
某创业公司开发了一个「文章总结」AI功能,但没有部署速率限制。一个恶意用户编写脚本在几分钟内发起了1万次请求。仅AI API成本就导致10分钟内产生了9600美元的费用,攻击持续了4小时未被发现,总损失超过20万美元。
Our Rate Limiting Architecture
我们的速率限制架构
Implementation Features
实现特性
- 5 requests per minute per IP address - Balances usability and security
- In-memory tracking - Fast, no database overhead
- IP-based identification - Works behind proxies via x-forwarded-for
- HTTP 429 status - Standard "Too Many Requests" response
- Shared budget - All routes using withRateLimit() share same 5/min limit per IP
- 每个IP地址每分钟5次请求 - 平衡易用性与安全性
- 内存级追踪 - 速度快,无数据库开销
- 基于IP的身份识别 - 可通过x-forwarded-for在代理后正常工作
- HTTP 429状态码 - 标准的「请求过多」响应
- 共享配额 - 所有使用withRateLimit()的路由对同一IP共享每分钟5次的限制
Why 5 Requests Per Minute?
为什么设置为每分钟5次请求?
Research on usability vs security shows that legitimate users rarely make more than 5 requests per minute to the same endpoint. This limit:
- ✅ Stops automated attacks
- ✅ Doesn't impact real users
- ✅ Allows reasonable form resubmissions
- ✅ Permits error recovery attempts
易用性与安全性的相关研究表明,合法用户很少会在一分钟内向同一个端点发起超过5次请求。该限制:
- ✅ 阻断自动化攻击
- ✅ 不影响真实用户使用
- ✅ 允许合理的表单重复提交
- ✅ 允许错误恢复尝试
Why Per-IP Tracking?
为什么基于IP追踪?
- Individual users get individual limits
- An attack on one IP doesn't block others
- During distributed attack, each bot IP limited separately
- Makes attacks ineffective at scale
- 每个用户有独立的限制额度
- 单个IP的攻击不会影响其他用户
- 分布式攻击中,每个机器人IP都会被单独限制
- 让大规模攻击失去效用
Implementation Files
实现文件
- - Rate limiting middleware
lib/withRateLimit.ts - - Test endpoint
app/api/test-rate-limit/route.ts - - Verification script
scripts/test-rate-limit.js
- - 速率限制中间件
lib/withRateLimit.ts - - 测试端点
app/api/test-rate-limit/route.ts - - 验证脚本
scripts/test-rate-limit.js
How to Use Rate Limiting
如何使用速率限制
Basic Usage
基础用法
For any endpoint that could be abused:
typescript
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
async function handler(request: NextRequest) {
// Your business logic
return NextResponse.json({ success: true });
}
// Apply rate limiting
export const POST = withRateLimit(handler);
export const config = {
runtime: 'nodejs',
};对于任何可能被滥用的端点:
typescript
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
async function handler(request: NextRequest) {
// 你的业务逻辑
return NextResponse.json({ success: true });
}
// 应用速率限制
export const POST = withRateLimit(handler);
export const config = {
runtime: 'nodejs',
};Combined with CSRF Protection
结合CSRF防护
For maximum security on state-changing operations:
typescript
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
import { withCsrf } from '@/lib/withCsrf';
async function handler(request: NextRequest) {
// Business logic
return NextResponse.json({ success: true });
}
// Layer both protections (rate limit first, then CSRF)
export const POST = withRateLimit(withCsrf(handler));
export const config = {
runtime: 'nodejs',
};对于修改数据的操作,可实现最高级别的安全防护:
typescript
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
import { withCsrf } from '@/lib/withCsrf';
async function handler(request: NextRequest) {
// 业务逻辑
return NextResponse.json({ success: true });
}
// 叠加两层防护(先速率限制,再CSRF)
export const POST = withRateLimit(withCsrf(handler));
export const config = {
runtime: 'nodejs',
};When to Apply Rate Limiting
何时需要应用速率限制
✅ Always Apply To:
- Contact/support forms
- Newsletter signup
- Account creation
- Password reset requests
- File upload endpoints
- Search endpoints
- Data export endpoints
- Any expensive AI/API operations
- Webhook endpoints
- Comment/review submission
- Report generation
- Bulk operations
❌ Usually Not Needed For:
- Static asset requests (handled by CDN)
- Simple GET endpoints that only read public data
- Health check endpoints
- Endpoints already protected by authentication rate limits
✅ 始终需要应用的场景:
- 联系/支持表单
- 通讯订阅注册
- 账号创建
- 密码重置请求
- 文件上传端点
- 搜索端点
- 数据导出端点
- 任何高成本的AI/API操作
- Webhook端点
- 评论/评价提交
- 报告生成
- 批量操作
❌ 通常不需要的场景:
- 静态资源请求(由CDN处理)
- 仅读取公开数据的简单GET端点
- 健康检查端点
- 已经受身份认证速率限制保护的端点
Complete Examples
完整示例
Example 1: Contact Form with Full Protection
示例1:具备完整防护的联系表单
typescript
// app/api/contact/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
import { withCsrf } from '@/lib/withCsrf';
import { validateRequest } from '@/lib/validateRequest';
import { contactFormSchema } from '@/lib/validation';
import { handleApiError } from '@/lib/errorHandler';
async function contactHandler(request: NextRequest) {
try {
const body = await request.json();
const validation = validateRequest(contactFormSchema, body);
if (!validation.success) {
return validation.response;
}
const { name, email, subject, message } = validation.data;
await sendEmail({
to: 'support@yourapp.com',
from: email,
subject,
message
});
return NextResponse.json({ success: true });
} catch (error) {
return handleApiError(error, 'contact-form');
}
}
export const POST = withRateLimit(withCsrf(contactHandler));
export const config = {
runtime: 'nodejs',
};typescript
// app/api/contact/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
import { withCsrf } from '@/lib/withCsrf';
import { validateRequest } from '@/lib/validateRequest';
import { contactFormSchema } from '@/lib/validation';
import { handleApiError } from '@/lib/errorHandler';
async function contactHandler(request: NextRequest) {
try {
const body = await request.json();
const validation = validateRequest(contactFormSchema, body);
if (!validation.success) {
return validation.response;
}
const { name, email, subject, message } = validation.data;
await sendEmail({
to: 'support@yourapp.com',
from: email,
subject,
message
});
return NextResponse.json({ success: true });
} catch (error) {
return handleApiError(error, 'contact-form');
}
}
export const POST = withRateLimit(withCsrf(contactHandler));
export const config = {
runtime: 'nodejs',
};Example 2: AI API Endpoint (Cost Protection)
示例2:AI API端点(成本防护)
typescript
// app/api/summarize/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
import { auth } from '@clerk/nextjs/server';
import { handleApiError, handleUnauthorizedError } from '@/lib/errorHandler';
import OpenAI from 'openai';
const openai = new OpenAI();
async function summarizeHandler(request: NextRequest) {
try {
// Require authentication for expensive operations
const { userId } = await auth();
if (!userId) return handleUnauthorizedError();
const { text } = await request.json();
// Rate limiting prevents abuse of expensive AI API
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [
{ role: 'system', content: 'Summarize the following text concisely.' },
{ role: 'user', content: text }
],
max_tokens: 150
});
return NextResponse.json({
summary: response.choices[0].message.content
});
} catch (error) {
return handleApiError(error, 'summarize');
}
}
// Protect expensive AI operations with rate limiting
export const POST = withRateLimit(summarizeHandler);
export const config = {
runtime: 'nodejs',
};typescript
// app/api/summarize/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
import { auth } from '@clerk/nextjs/server';
import { handleApiError, handleUnauthorizedError } from '@/lib/errorHandler';
import OpenAI from 'openai';
const openai = new OpenAI();
async function summarizeHandler(request: NextRequest) {
try {
// 高成本操作需要身份认证
const { userId } = await auth();
if (!userId) return handleUnauthorizedError();
const { text } = await request.json();
// 速率限制防止高成本AI API被滥用
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [
{ role: 'system', content: 'Summarize the following text concisely.' },
{ role: 'user', content: text }
],
max_tokens: 150
});
return NextResponse.json({
summary: response.choices[0].message.content
});
} catch (error) {
return handleApiError(error, 'summarize');
}
}
// 用速率限制保护高成本AI操作
export const POST = withRateLimit(summarizeHandler);
export const config = {
runtime: 'nodejs',
};Example 3: File Upload Endpoint
示例3:文件上传端点
typescript
// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
import { auth } from '@clerk/nextjs/server';
import { handleApiError, handleUnauthorizedError } from '@/lib/errorHandler';
async function uploadHandler(request: NextRequest) {
try {
const { userId } = await auth();
if (!userId) return handleUnauthorizedError();
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json(
{ error: 'No file provided' },
{ status: 400 }
);
}
// Validate file size (10MB max)
if (file.size > 10 * 1024 * 1024) {
return NextResponse.json(
{ error: 'File too large (max 10MB)' },
{ status: 400 }
);
}
// Process upload
const uploadResult = await processFileUpload(file, userId);
return NextResponse.json({ success: true, fileId: uploadResult.id });
} catch (error) {
return handleApiError(error, 'upload');
}
}
// Prevent upload spam
export const POST = withRateLimit(uploadHandler);
export const config = {
runtime: 'nodejs',
};typescript
// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
import { auth } from '@clerk/nextjs/server';
import { handleApiError, handleUnauthorizedError } from '@/lib/errorHandler';
async function uploadHandler(request: NextRequest) {
try {
const { userId } = await auth();
if (!userId) return handleUnauthorizedError();
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json(
{ error: 'No file provided' },
{ status: 400 }
);
}
// 验证文件大小(最大10MB)
if (file.size > 10 * 1024 * 1024) {
return NextResponse.json(
{ error: 'File too large (max 10MB)' },
{ status: 400 }
);
}
// 处理上传
const uploadResult = await processFileUpload(file, userId);
return NextResponse.json({ success: true, fileId: uploadResult.id });
} catch (error) {
return handleApiError(error, 'upload');
}
}
// 防止上传垃圾内容
export const POST = withRateLimit(uploadHandler);
export const config = {
runtime: 'nodejs',
};Technical Implementation Details
技术实现细节
Rate Limiter Code (lib/withRateLimit.ts)
速率限制器代码 (lib/withRateLimit.ts)
typescript
import { NextRequest, NextResponse } from 'next/server';
// In-memory storage for rate limiting
const rateLimitStore = new Map<string, { count: number; resetAt: number }>();
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute in milliseconds
const MAX_REQUESTS = 5;
function getClientIp(request: NextRequest): string {
// Check for forwarded IP (when behind proxy/load balancer)
const forwarded = request.headers.get('x-forwarded-for');
if (forwarded) {
return forwarded.split(',')[0].trim();
}
const realIp = request.headers.get('x-real-ip');
if (realIp) {
return realIp;
}
// Fallback to direct IP
return request.ip || 'unknown';
}
function cleanupExpiredEntries() {
const now = Date.now();
for (const [key, value] of rateLimitStore.entries()) {
if (now > value.resetAt) {
rateLimitStore.delete(key);
}
}
}
export function withRateLimit(
handler: (request: NextRequest) => Promise<NextResponse>
) {
return async (request: NextRequest) => {
// Clean up expired entries periodically
cleanupExpiredEntries();
const clientIp = getClientIp(request);
const now = Date.now();
// Get or create rate limit entry
let rateLimitEntry = rateLimitStore.get(clientIp);
if (!rateLimitEntry || now > rateLimitEntry.resetAt) {
// Create new entry or reset expired one
rateLimitEntry = {
count: 0,
resetAt: now + RATE_LIMIT_WINDOW
};
rateLimitStore.set(clientIp, rateLimitEntry);
}
// Increment request count
rateLimitEntry.count++;
// Check if limit exceeded
if (rateLimitEntry.count > MAX_REQUESTS) {
const retryAfter = Math.ceil((rateLimitEntry.resetAt - now) / 1000);
return NextResponse.json(
{
error: 'Too many requests',
message: `Rate limit exceeded. Please try again in ${retryAfter} seconds.`,
retryAfter
},
{
status: 429,
headers: {
'Retry-After': retryAfter.toString(),
'X-RateLimit-Limit': MAX_REQUESTS.toString(),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': rateLimitEntry.resetAt.toString()
}
}
);
}
// Add rate limit headers to response
const response = await handler(request);
response.headers.set('X-RateLimit-Limit', MAX_REQUESTS.toString());
response.headers.set(
'X-RateLimit-Remaining',
(MAX_REQUESTS - rateLimitEntry.count).toString()
);
response.headers.set('X-RateLimit-Reset', rateLimitEntry.resetAt.toString());
return response;
};
}typescript
import { NextRequest, NextResponse } from 'next/server';
// 速率限制的内存存储
const rateLimitStore = new Map<string, { count: number; resetAt: number }>();
const RATE_LIMIT_WINDOW = 60 * 1000; // 1分钟,单位毫秒
const MAX_REQUESTS = 5;
function getClientIp(request: NextRequest): string {
// 检查转发IP(位于代理/负载均衡器后时使用)
const forwarded = request.headers.get('x-forwarded-for');
if (forwarded) {
return forwarded.split(',')[0].trim();
}
const realIp = request.headers.get('x-real-ip');
if (realIp) {
return realIp;
}
// fallback到直接连接IP
return request.ip || 'unknown';
}
function cleanupExpiredEntries() {
const now = Date.now();
for (const [key, value] of rateLimitStore.entries()) {
if (now > value.resetAt) {
rateLimitStore.delete(key);
}
}
}
export function withRateLimit(
handler: (request: NextRequest) => Promise<NextResponse>
) {
return async (request: NextRequest) => {
// 定期清理过期条目
cleanupExpiredEntries();
const clientIp = getClientIp(request);
const now = Date.now();
// 获取或创建速率限制条目
let rateLimitEntry = rateLimitStore.get(clientIp);
if (!rateLimitEntry || now > rateLimitEntry.resetAt) {
// 创建新条目或重置过期条目
rateLimitEntry = {
count: 0,
resetAt: now + RATE_LIMIT_WINDOW
};
rateLimitStore.set(clientIp, rateLimitEntry);
}
// 增加请求计数
rateLimitEntry.count++;
// 检查是否超过限制
if (rateLimitEntry.count > MAX_REQUESTS) {
const retryAfter = Math.ceil((rateLimitEntry.resetAt - now) / 1000);
return NextResponse.json(
{
error: 'Too many requests',
message: `Rate limit exceeded. Please try again in ${retryAfter} seconds.`,
retryAfter
},
{
status: 429,
headers: {
'Retry-After': retryAfter.toString(),
'X-RateLimit-Limit': MAX_REQUESTS.toString(),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': rateLimitEntry.resetAt.toString()
}
}
);
}
// 向响应添加速率限制头
const response = await handler(request);
response.headers.set('X-RateLimit-Limit', MAX_REQUESTS.toString());
response.headers.set(
'X-RateLimit-Remaining',
(MAX_REQUESTS - rateLimitEntry.count).toString()
);
response.headers.set('X-RateLimit-Reset', rateLimitEntry.resetAt.toString());
return response;
};
}Client-Side Handling
客户端处理
Graceful Degradation
优雅降级
typescript
async function submitForm(data: FormData) {
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.status === 429) {
// Rate limited
const result = await response.json();
alert(`Too many requests. Please wait ${result.retryAfter} seconds.`);
return;
}
if (response.ok) {
alert('Form submitted successfully!');
} else {
alert('Submission failed. Please try again.');
}
} catch (error) {
console.error('Error:', error);
alert('An error occurred. Please try again.');
}
}typescript
async function submitForm(data: FormData) {
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.status === 429) {
// 触发速率限制
const result = await response.json();
alert(`Too many requests. Please wait ${result.retryAfter} seconds.`);
return;
}
if (response.ok) {
alert('表单提交成功!');
} else {
alert('提交失败,请重试。');
}
} catch (error) {
console.error('错误:', error);
alert('发生错误,请重试。');
}
}Automatic Retry with Exponential Backoff
指数退避自动重试
typescript
async function submitWithRetry(
data: FormData,
maxRetries = 3
): Promise<Response> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.status !== 429) {
return response; // Success or non-rate-limit error
}
// Get retry-after header
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
if (attempt < maxRetries - 1) {
// Wait with exponential backoff
const delay = Math.min(retryAfter * 1000 * (2 ** attempt), 60000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Max retries exceeded');
}typescript
async function submitWithRetry(
data: FormData,
maxRetries = 3
): Promise<Response> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.status !== 429) {
return response; // 成功或非速率限制错误
}
// 获取retry-after头
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
if (attempt < maxRetries - 1) {
// 指数退避等待
const delay = Math.min(retryAfter * 1000 * (2 ** attempt), 60000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('超过最大重试次数');
}Show Rate Limit Status
展示速率限制状态
typescript
'use client';
import { useState, useEffect } from 'react';
export function RateLimitStatus() {
const [limit, setLimit] = useState({ remaining: 5, total: 5 });
async function checkRateLimit() {
const response = await fetch('/api/test-rate-limit');
setLimit({
remaining: parseInt(response.headers.get('X-RateLimit-Remaining') || '5'),
total: parseInt(response.headers.get('X-RateLimit-Limit') || '5')
});
}
return (
<div>
<p>Requests remaining: {limit.remaining}/{limit.total}</p>
<button onClick={checkRateLimit}>Check Status</button>
</div>
);
}typescript
'use client';
import { useState, useEffect } from 'react';
export function RateLimitStatus() {
const [limit, setLimit] = useState({ remaining: 5, total: 5 });
async function checkRateLimit() {
const response = await fetch('/api/test-rate-limit');
setLimit({
remaining: parseInt(response.headers.get('X-RateLimit-Remaining') || '5'),
total: parseInt(response.headers.get('X-RateLimit-Limit') || '5')
});
}
return (
<div>
<p>剩余请求次数: {limit.remaining}/{limit.total}</p>
<button onClick={checkRateLimit}>检查状态</button>
</div>
);
}Attack Scenarios & Protection
攻击场景与防护方案
Attack 1: Brute Force Login
攻击1:暴力破解登录
Attack:
bash
undefined攻击方式:
bash
undefinedAttacker tries multiple passwords
攻击者尝试多个密码
for i in {1..1000}; do
curl -X POST https://yourapp.com/api/login
-d "username=victim&password=attempt$i" done
-d "username=victim&password=attempt$i" done
**Protection:**
- Requests 1-5: Processed normally
- Requests 6+: Blocked with HTTP 429
- Attack stopped after 5 attempts
- Attacker must wait 60 seconds between batchesfor i in {1..1000}; do
curl -X POST https://yourapp.com/api/login
-d "username=victim&password=attempt$i" done
-d "username=victim&password=attempt$i" done
**防护效果:**
- 第1-5次请求:正常处理
- 第6次及以上请求:返回HTTP 429被阻断
- 攻击在5次尝试后被阻止
- 攻击者每批尝试之间需要等待60秒Attack 2: Form Spam
攻击2:表单垃圾提交
Attack:
javascript
// Bot spams contact form
for (let i = 0; i < 1000; i++) {
fetch('/api/contact', {
method: 'POST',
body: JSON.stringify({ message: 'spam' })
});
}Protection:
- First 5 requests: Accepted
- Subsequent requests: HTTP 429
- No email flooding
- No database spam
攻击方式:
javascript
// 机器人 spam 联系表单
for (let i = 0; i < 1000; i++) {
fetch('/api/contact', {
method: 'POST',
body: JSON.stringify({ message: 'spam' })
});
}防护效果:
- 前5次请求:被接受
- 后续请求:返回HTTP 429
- 不会出现邮件轰炸
- 不会产生数据库垃圾数据
Attack 3: Resource Exhaustion (AI APIs)
攻击3:资源耗尽(AI APIs)
Attack:
bash
undefined攻击方式:
bash
undefinedAttacker abuses expensive AI endpoint
攻击者滥用高成本AI端点
while true; do
curl -X POST https://yourapp.com/api/summarize
-d '{"text": "very long text..."}' done
-d '{"text": "very long text..."}' done
**Protection:**
- Limited to 5 AI calls per minute per IP
- Prevents $1000s in unexpected API charges
- Legitimate users unaffectedwhile true; do
curl -X POST https://yourapp.com/api/summarize
-d '{"text": "very long text..."}' done
-d '{"text": "very long text..."}' done
**防护效果:**
- 每个IP每分钟最多调用5次AI接口
- 避免了数千美元的意外API费用
- 合法用户不受影响Attack 4: Distributed Attack
攻击4:分布式攻击
Attack:
Multiple IPs attacking simultaneously
Protection:
- Each IP tracked separately
- 5 requests/min limit per IP
- Attack must use 1000 IPs to make 5000 req/min
- More expensive/difficult for attacker
攻击方式:
多个IP同时发起攻击
防护效果:
- 每个IP单独追踪
- 每个IP限制为每分钟5次请求
- 攻击者需要使用1000个IP才能达到每分钟5000次请求
- 大幅提升攻击者的攻击成本和难度
Testing Rate Limiting
测试速率限制
Manual Testing
手动测试
bash
undefinedbash
undefinedTest rate limiting
测试速率限制
for i in {1..10}; do
echo "Request $i:"
curl -s -o /dev/null -w "%{http_code}\n"
http://localhost:3000/api/test-rate-limit sleep 0.1 done
http://localhost:3000/api/test-rate-limit sleep 0.1 done
for i in {1..10}; do
echo "Request $i:"
curl -s -o /dev/null -w "%{http_code}\n"
http://localhost:3000/api/test-rate-limit sleep 0.1 done
http://localhost:3000/api/test-rate-limit sleep 0.1 done
Expected output:
预期输出:
Request 1: 200
Request 1: 200
Request 2: 200
Request 2: 200
Request 3: 200
Request 3: 200
Request 4: 200
Request 4: 200
Request 5: 200
Request 5: 200
Request 6: 429
Request 6: 429
Request 7: 429
Request 7: 429
Request 8: 429
Request 8: 429
Request 9: 429
Request 9: 429
Request 10: 429
Request 10: 429
undefinedundefinedAutomated Test Script
自动化测试脚本
bash
undefinedbash
undefinedRun the provided test script
运行提供的测试脚本
node scripts/test-rate-limit.js
node scripts/test-rate-limit.js
Expected output:
预期输出:
Testing Rate Limiting (5 requests/minute per IP)
Testing Rate Limiting (5 requests/minute per IP)
Request 1: ✓ 200 - Success
Request 1: ✓ 200 - Success
Request 2: ✓ 200 - Success
Request 2: ✓ 200 - Success
Request 3: ✓ 200 - Success
Request 3: ✓ 200 - Success
Request 4: ✓ 200 - Success
Request 4: ✓ 200 - Success
Request 5: ✓ 200 - Success
Request 5: ✓ 200 - Success
Request 6: ✗ 429 - Too many requests
Request 6: ✗ 429 - Too many requests
Request 7: ✗ 429 - Too many requests
Request 7: ✗ 429 - Too many requests
Request 8: ✗ 429 - Too many requests
Request 8: ✗ 429 - Too many requests
Request 9: ✗ 429 - Too many requests
Request 9: ✗ 429 - Too many requests
Request 10: ✗ 429 - Too many requests
Request 10: ✗ 429 - Too many requests
✓ Rate limiting is working correctly!
✓ Rate limiting is working correctly!
undefinedundefinedTest Reset After Window
测试窗口过期后重置
bash
undefinedbash
undefinedMake 5 requests
发起5次请求
for i in {1..5}; do
curl http://localhost:3000/api/test-rate-limit
done
for i in {1..5}; do
curl http://localhost:3000/api/test-rate-limit
done
Wait 61 seconds
等待61秒
sleep 61
sleep 61
Try again - should succeed
再次尝试 - 应该成功
Expected: 200 OK (limit reset)
预期:200 OK(限制已重置)
undefinedundefinedAdvanced: Custom Rate Limits
进阶:自定义速率限制
If you need different limits for different endpoints:
typescript
// lib/withCustomRateLimit.ts
import { NextRequest, NextResponse } from 'next/server';
const rateLimitStore = new Map<string, { count: number; resetAt: number }>();
export function withCustomRateLimit(
maxRequests: number,
windowMs: number
) {
return (handler: (request: NextRequest) => Promise<NextResponse>) => {
return async (request: NextRequest) => {
const clientIp = getClientIp(request);
const now = Date.now();
const key = `${clientIp}:${request.url}`;
let entry = rateLimitStore.get(key);
if (!entry || now > entry.resetAt) {
entry = { count: 0, resetAt: now + windowMs };
rateLimitStore.set(key, entry);
}
entry.count++;
if (entry.count > maxRequests) {
return NextResponse.json(
{ error: 'Too many requests' },
{ status: 429 }
);
}
return handler(request);
};
};
}
// Usage:
// export const POST = withCustomRateLimit(10, 60000)(handler); // 10 req/min
// export const POST = withCustomRateLimit(100, 3600000)(handler); // 100 req/hour如果不同端点需要不同的限制:
typescript
// lib/withCustomRateLimit.ts
import { NextRequest, NextResponse } from 'next/server';
const rateLimitStore = new Map<string, { count: number; resetAt: number }>();
export function withCustomRateLimit(
maxRequests: number,
windowMs: number
) {
return (handler: (request: NextRequest) => Promise<NextResponse>) => {
return async (request: NextRequest) => {
const clientIp = getClientIp(request);
const now = Date.now();
const key = `${clientIp}:${request.url}`;
let entry = rateLimitStore.get(key);
if (!entry || now > entry.resetAt) {
entry = { count: 0, resetAt: now + windowMs };
rateLimitStore.set(key, entry);
}
entry.count++;
if (entry.count > maxRequests) {
return NextResponse.json(
{ error: 'Too many requests' },
{ status: 429 }
);
}
return handler(request);
};
};
}
// 用法:
// export const POST = withCustomRateLimit(10, 60000)(handler); // 10次/分钟
// export const POST = withCustomRateLimit(100, 3600000)(handler); // 100次/小时Production Considerations
生产环境注意事项
Redis-Based Rate Limiting (Recommended for Production)
基于Redis的速率限制(生产环境推荐)
For production deployments with multiple servers, use Redis:
typescript
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
export function withRedisRateLimit(
handler: (request: NextRequest) => Promise<NextResponse>
) {
return async (request: NextRequest) => {
const clientIp = getClientIp(request);
const key = `rate-limit:${clientIp}`;
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, 60); // 60 second window
}
if (current > 5) {
const ttl = await redis.ttl(key);
return NextResponse.json(
{ error: 'Too many requests', retryAfter: ttl },
{ status: 429 }
);
}
return handler(request);
};
}对于多服务器的生产部署,使用Redis:
typescript
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
export function withRedisRateLimit(
handler: (request: NextRequest) => Promise<NextResponse>
) {
return async (request: NextRequest) => {
const clientIp = getClientIp(request);
const key = `rate-limit:${clientIp}`;
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, 60); // 60秒窗口
}
if (current > 5) {
const ttl = await redis.ttl(key);
return NextResponse.json(
{ error: 'Too many requests', retryAfter: ttl },
{ status: 429 }
);
}
return handler(request);
};
}Logging Rate Limit Violations
记录速率限制违规日志
typescript
import { logSecurityEvent } from '@/lib/security-logger';
// In withRateLimit function, when limit exceeded:
if (rateLimitEntry.count > MAX_REQUESTS) {
// Log potential attack
logSecurityEvent({
type: 'RATE_LIMIT_EXCEEDED',
ip: clientIp,
path: request.nextUrl.pathname,
count: rateLimitEntry.count,
timestamp: new Date().toISOString()
});
return NextResponse.json(/* ... */);
}typescript
import { logSecurityEvent } from '@/lib/security-logger';
// 在withRateLimit函数中,当超过限制时:
if (rateLimitEntry.count > MAX_REQUESTS) {
// 记录潜在攻击
logSecurityEvent({
type: 'RATE_LIMIT_EXCEEDED',
ip: clientIp,
path: request.nextUrl.pathname,
count: rateLimitEntry.count,
timestamp: new Date().toISOString()
});
return NextResponse.json(/* ... */);
}What Rate Limiting Prevents
速率限制的防护范围
✅ Brute force password attacks - Main protection
✅ Credential stuffing - Automated login attempts
✅ API abuse and spam - Prevents bot spam
✅ Resource exhaustion (DoS) - Prevents overwhelming server
✅ Excessive costs from AI/paid APIs - Cost protection
✅ Scraping and data harvesting - Slows down bulk collection
✅ Account enumeration - Prevents discovering valid accounts
✅ 暴力破解密码攻击 - 核心防护能力
✅ 凭证填塞 - 防范自动化登录尝试
✅ API滥用与垃圾提交 - 防范机器人垃圾信息
✅ 资源耗尽(DoS) - 避免服务器被压垮
✅ AI/付费API的超额成本 - 成本防护
✅ 爬虫与数据窃取 - 减缓批量数据收集
✅ 账号枚举 - 防范有效账号被探测
Common Mistakes to Avoid
需要避免的常见错误
❌ DON'T skip rate limiting on expensive operations
❌ DON'T use the same endpoint for different operations without separate limits
❌ DON'T rely solely on client-side rate limiting
❌ DON'T forget to handle HTTP 429 responses gracefully in frontend
❌ DON'T set limits too high (defeats purpose) or too low (hurts UX)
✅ DO apply to all public endpoints that could be abused
✅ DO use Redis in production for multi-server deployments
✅ DO log rate limit violations for security monitoring
✅ DO provide clear error messages with retry-after time
✅ DO combine with CSRF for maximum protection
❌ 不要在高成本操作上跳过速率限制
❌ 不要为不同操作使用同一个端点却不设置独立限制
❌ 不要仅依赖客户端侧的速率限制
❌ 不要忘记在前端优雅处理HTTP 429响应
❌ 不要把限制设置得太高(失去防护意义)或太低(影响用户体验)
✅ 务必为所有可能被滥用的公开端点部署速率限制
✅ 生产环境多服务器部署时务必使用Redis
✅ 务必记录速率限制违规日志用于安全监控
✅ 务必提供清晰的错误信息和重试等待时间
✅ 务必结合CSRF实现最高级别的防护
References
参考资料
- OWASP API Security Top 10 - Unrestricted Resource Consumption: https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/
- Rate Limiting Best Practices: https://cloud.google.com/architecture/rate-limiting-strategies-techniques
- HTTP 429 Status Code: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429
Next Steps
后续步骤
- For CSRF protection: Use skill
csrf-protection - For input validation: Use skill
input-validation - For testing rate limiting: Use skill
security-testing - For complete API security: Combine rate limiting + CSRF + validation
- CSRF防护:使用skill
csrf-protection - 输入验证:使用skill
input-validation - 速率限制测试:使用skill
security-testing - 完整API安全:结合速率限制 + CSRF + 验证能力