rate-limiting

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Rate 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

实现文件

  • lib/withRateLimit.ts
    - Rate limiting middleware
  • app/api/test-rate-limit/route.ts
    - Test endpoint
  • scripts/test-rate-limit.js
    - Verification script
  • 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
undefined

Attacker tries multiple passwords

攻击者尝试多个密码

for i in {1..1000}; do curl -X POST https://yourapp.com/api/login
-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 batches
for i in {1..1000}; do curl -X POST https://yourapp.com/api/login
-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
undefined

Attacker abuses expensive AI endpoint

攻击者滥用高成本AI端点

while true; do curl -X POST https://yourapp.com/api/summarize
-d '{"text": "very long text..."}' done

**Protection:**
- Limited to 5 AI calls per minute per IP
- Prevents $1000s in unexpected API charges
- Legitimate users unaffected
while true; do curl -X POST https://yourapp.com/api/summarize
-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
undefined
bash
undefined

Test 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
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

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

undefined
undefined

Automated Test Script

自动化测试脚本

bash
undefined
bash
undefined

Run 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!

undefined
undefined

Test Reset After Window

测试窗口过期后重置

bash
undefined
bash
undefined

Make 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(限制已重置)

undefined
undefined

Advanced: 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 operationsDON'T use the same endpoint for different operations without separate limitsDON'T rely solely on client-side rate limitingDON'T forget to handle HTTP 429 responses gracefully in frontendDON'T set limits too high (defeats purpose) or too low (hurts UX)
DO apply to all public endpoints that could be abusedDO use Redis in production for multi-server deploymentsDO log rate limit violations for security monitoringDO provide clear error messages with retry-after timeDO combine with CSRF for maximum protection
不要在高成本操作上跳过速率限制不要为不同操作使用同一个端点却不设置独立限制不要仅依赖客户端侧的速率限制不要忘记在前端优雅处理HTTP 429响应不要把限制设置得太高(失去防护意义)或太低(影响用户体验)
务必为所有可能被滥用的公开端点部署速率限制生产环境多服务器部署时务必使用Redis务必记录速率限制违规日志用于安全监控务必提供清晰的错误信息和重试等待时间务必结合CSRF实现最高级别的防护

References

参考资料

Next Steps

后续步骤

  • For CSRF protection: Use
    csrf-protection
    skill
  • For input validation: Use
    input-validation
    skill
  • For testing rate limiting: Use
    security-testing
    skill
  • For complete API security: Combine rate limiting + CSRF + validation
  • CSRF防护:使用
    csrf-protection
    skill
  • 输入验证:使用
    input-validation
    skill
  • 速率限制测试:使用
    security-testing
    skill
  • 完整API安全:结合速率限制 + CSRF + 验证能力