Menu

登录速率限制配置指南

本文档说明如何为 NEXTY.DEV 模板中的身份验证端点配置基于 IP 的速率限制。速率限制有助于防止滥用行为,如邮件垃圾、暴力破解攻击和过度使用免费套餐。

概述

NEXTY.DEV 默认使用 Better Auth 内置的速率限制功能,并可选地支持 Upstash Redis 以适配无服务器环境。

默认配置

lib/auth/index.ts
rateLimit: {
  enabled: true,
  window: 60, // 60 秒
  max: 100,   // 每个时间窗口 100 个请求(全局默认值)
  customRules: {
    "/sign-in/magic-link": { window: 60, max: 3 },
    "/email-otp/send-verification-otp": { window: 60, max: 3 },
    "/sign-in/email-otp": { window: 60, max: 5 },
  },
}
端点时间窗口最大请求数说明
/sign-in/magic-link60秒3Magic Link 登录邮件请求
/email-otp/send-verification-otp60秒3OTP 验证码邮件请求
/sign-in/email-otp60秒5OTP 验证码尝试次数
其他所有端点60秒100全局默认值

自定义示例

场景 1:更严格的限制以防止免费套餐滥用

如果你担心用户通过切换账号来滥用免费套餐积分,可以添加更严格的每日限制:

lib/auth/index.ts
rateLimit: {
  enabled: true,
  window: 60,
  max: 100,
  customRules: {
    // 严格的邮件发送限制
    "/sign-in/magic-link": { 
      window: 60 * 60 * 24, // 24 小时
      max: 5, // 每个 IP 每天只能发送 5 个魔法链接
    },
    "/email-otp/send-verification-otp": { 
      window: 60 * 60 * 24, // 24 小时
      max: 10, // 每个 IP 每天只能发送 10 个 OTP 验证码
    },
    // 保持验证尝试在合理范围
    "/sign-in/email-otp": { 
      window: 60 * 60 * 24, // 24 小时
      max: 20, // 每 24 小时 20 次尝试
    }
  },
  ...(redis && {
    customStorage: {
      get: async (key: string) => {
        const data = await redis!.get<{ key: string; count: number; lastRequest: number }>(key);
        return data || undefined;
      },
      set: async (key: string, value: { key: string; count: number; lastRequest: number }) => {
        await redis!.set(key, value, { ex: 60 * 60 * 24 }); // 24 小时后自动过期
      },
    },
  }),
}

场景 2:非常严格的防滥用(高价值服务)

对于免费套餐滥用成本较高的服务(如 AI 积分、API 调用):

rateLimit: {
  enabled: true,
  window: 60,
  max: 50, // 降低全局默认值
  customRules: {
    // 极其严格的邮件限制
    "/sign-in/magic-link": { 
      window: 60 * 60 * 24, // 24 小时
      max: 3, // 每个 IP 每天只能发送 3 个魔法链接
    },
    "/email-otp/send-verification-otp": { 
      window: 60 * 60 * 24, // 24 小时
      max: 5, // 每个 IP 每天只能发送 5 个 OTP 验证码
    },
    // 严格的验证限制
    "/sign-in/email-otp": { 
      window: 60 * 60 * 24, // 24 小时
      max: 20, // 每 24 小时 20 次尝试
    },
  },
  ...(redis && {
    customStorage: {
      get: async (key: string) => {
        const data = await redis!.get<{ key: string; count: number; lastRequest: number }>(key);
        return data || undefined;
      },
      set: async (key: string, value: { key: string; count: number; lastRequest: number }) => {
        await redis!.set(key, value, { ex: 60 * 60 * 24 }); // 24 小时后自动过期
      },
    },
  }),
}

场景 3:宽松限制(内部工具 / 低风险)

对于内部工具或低风险应用程序:

rateLimit: {
  enabled: true,
  window: 60,
  max: 200, // 提高全局默认值
  customRules: {
    "/sign-in/magic-link": { window: 60, max: 10 },
    "/email-otp/send-verification-otp": { window: 60, max: 10 },
    "/sign-in/email-otp": { window: 60, max: 20 },
  },
}

场景 4:禁用速率限制(仅限开发环境)

rateLimit: {
  enabled: false, // ⚠️ 切勿在生产环境中这样做!
}

存储选项

内存存储(默认)

没有你没有配置 Upstash Redis 环境变量,那么模板内置的 Redis 方法不会启用,这种情况速率限制数据将存储在内存中。

优点:

  • 无需外部依赖
  • 设置简单

缺点:

  • 服务器重启后数据丢失
  • 不能在多个实例之间共享,部署在 Vercel 等无服务器环境时,每个实例都有自己的内存,这会使速率限制失效

Upstash Redis 存储(生产环境推荐)

当配置了 UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN 时,模板会自动使用 Redis。

lib/auth/index.ts
...(redis && {
  customStorage: {
    get: async (key: string) => {
      const data = await redis!.get<{ key: string; count: number; lastRequest: number }>(key);
      return data || undefined;
    },
    set: async (key: string, value: { key: string; count: number; lastRequest: number }) => {
      await redis!.set(key, value, { ex: 120 }); // 120 秒后自动过期
    },
  },
}),

优点:

  • 数据在重启后保持
  • 在所有实例之间共享
  • 完美适配无服务器环境(Vercel、Cloudflare Workers)
  • 通过 TTL 自动清理数据

缺点:

  • 需要 Upstash 账号(有免费套餐)

设置 Upstash Redis

查看 Upstash 集成步骤。

数据库存储

提示

  • 这种方案模板没有内置支持,如果你想用此方案,请根据文档自己实现。
  • 数据库存储不会自动清理过期记录。你需要设置定时任务来清理旧记录。

Better Auth 也支持数据库存储:

lib/auth/index.ts
rateLimit: {
  enabled: true,
  storage: "database",
  modelName: "rateLimit", // 表名
  // ...
}

然后运行迁移:

pnpm dlx @better-auth/cli migrate