这个 Nuxt 模块实现了一个速率限制中间件,以保护您的 API 端点免受过多请求。
Retry-After 头。nuxt-api-shield 依赖项添加到您的项目# Using pnpm
pnpm add nuxt-api-shield
# Using yarn
yarn add nuxt-api-shield
# Using npm
npm install nuxt-api-shield
nuxt-api-shield 添加到 nuxt.config.ts 的 modules 部分您应该只添加您想与默认值不同的值。
export default defineNuxtConfig({
modules: ["nuxt-api-shield"],
nuxtApiShield: {
/*limit: {
max: 12, // maximum requests per duration time, default is 12/duration
duration: 108, // duration time in seconds, default is 108 seconds
ban: 3600, // ban time in seconds, default is 3600 seconds = 1 hour
// If the request limit is exceeded, the user is banned for this period. During the ban, all requests are blocked with 429.
},
delayOnBan: true // delay every response with +1sec when the user is banned, default is true
errorMessage: "Too Many Requests", // error message when the user is banned, default is "Too Many Requests"
retryAfterHeader: false, // when the user is banned add the Retry-After header to the response, default is false
log: {
path: "logs", // path to the log file, every day a new log file will be created, use "" to disable logging
attempts: 100, // if an IP reach 100 requests, all the requests will be logged, can be used for further analysis or blocking for example with fail2ban, use 0 to disable logging
},
routes: [], // specify routes to apply rate limiting to, default is an empty array meaning all routes are protected.
// Example:
// routes: ["/api/v2/", "/api/v3/"], // /api/v1 will not be protected, /api/v2/ and /api/v3/ will be protected */
ipTTL: 604800, // Optional: Time-to-live in seconds for IP tracking entries (default: 7 days). Set to 0 or negative to disable this specific cleanup.
security: { // Optional: Security-related configurations
trustXForwardedFor: true, // Default: true. Whether to trust X-Forwarded-For headers. See warning below.
}
},
});
**默认配置值:** (如果未在您的 nuxtApiShield 配置中指定,模块将应用这些值)
{
limit: {
max: 12,
duration: 108, // seconds
ban: 3600, // seconds
},
delayOnBan: true,
errorMessage: "Too Many Requests",
retryAfterHeader: false,
log: {
path: "logs", // Logging is disabled if path is empty
attempts: 100, // Logging per IP is disabled if attempts is 0
},
routes: [],
ipTTL: 7 * 24 * 60 * 60, // 7 days in seconds
security: {
trustXForwardedFor: true,
}
}
安全警告:trustXForwardedFor
security.trustXForwardedFor 选项(默认值为 true,由模块设置)确定模块是否使用 X-Forwarded-For HTTP 头来识别客户端的 IP 地址。
true:模块将使用 X-Forwarded-For 头中提供的 IP 地址。这在您的 Nuxt 应用程序位于受信任的反向代理、负载均衡器或 CDN(如 Nginx、Cloudflare、AWS ELB/ALB)之后,并且这些服务正确设置了带有真实客户端 IP 的此头时很常见。trustXForwardedFor 为 true 并且您的应用程序直接面向互联网,或者您的代理未配置为从客户端剥离传入的 X-Forwarded-For 头,恶意用户可以通过发送伪造的 X-Forwarded-For 头来欺骗他们的 IP 地址。这将使他们能够绕过速率限制或导致其他用户被错误地速率限制。false:模块将使用传入连接的直接 IP 地址(即 event.node.req.socket.remoteAddress)。如果您的应用程序直接面向互联网,或者您不确定您的代理配置,请使用此设置。trustXForwardedFor: true。否则,将其设置为 false。nitro/storage 添加到 nuxt.config.ts您可以使用任何您想要的存储,但必须使用 **shield** 作为存储名称。
{
"nitro": {
"storage": {
"shield": {
// storage name, you **must** use "shield" as the name
"driver": "memory"
}
}
}
}
例如,如果您使用 Redis,您可以使用以下配置,定义主机和端口。
{
"nitro": {
"storage": {
"shield": {
"driver": "redis",
"host": "localhost",
"port": 6379,
}
}
}
}
nuxt.config.ts{
"nitro": {
"experimental": {
"tasks": true
},
"scheduledTasks": {
"*/15 * * * *": ["shield:cleanBans"], // Example: clean expired bans every 15 minutes
"0 0 * * *": ["shield:cleanIpData"] // Example: clean old IP data daily at midnight
}
}
}
建议定期清理过期的禁用和旧的 IP 跟踪数据,以防止存储膨胀并确保良好的性能。
此任务在禁用期限过后从存储中删除禁用条目 (ban:xxx.xxx.xxx.xxx)。
在 server/tasks/shield/cleanBans.ts 中(您可以随意命名文件和任务)
import { isActualBanTimestampExpired } from '#imports'; // Auto-imported utility from nuxt-api-shield
export default defineTask({
meta: {
name: 'shield:cleanBans', // Match the name in scheduledTasks
description: 'Clean expired bans from nuxt-api-shield storage.',
},
async run() {
const shieldStorage = useStorage('shield'); // Use your configured storage name
// Only fetch keys that start with the 'ban:' prefix
const banKeys = await shieldStorage.getKeys('ban:');
let cleanedCount = 0;
for (const key of banKeys) {
const bannedUntilRaw = await shieldStorage.getItem(key);
if (isActualBanTimestampExpired(bannedUntilRaw)) {
await shieldStorage.removeItem(key);
cleanedCount++;
}
}
console.log(`[nuxt-api-shield] Cleaned ${cleanedCount} expired ban(s).`);
return { result: { cleanedCount } };
},
});
isActualBanTimestampExpired 实用程序由 nuxt-api-shield 提供,并且应该通过 #imports 可用。
此任务清理在一定时期内不活跃(即其 time 字段未更新)的 IP 跟踪条目 (ip:xxx.xxx.xxx.xxx)。此期限由 nuxt.config.ts 中(在 nuxtApiShield 下)的 ipTTL 配置选项定义,默认为 7 天。此清理有助于防止您的存储因很少请求但从未被禁用的 IP 而无限增长。
在 server/tasks/shield/cleanIpData.ts 中
import type { RateLimit } from '#imports'; // Or from 'nuxt-api-shield/types' if made available by the module
import { useRuntimeConfig } from '#imports';
export default defineTask({
meta: {
name: 'shield:cleanIpData', // Match the name in scheduledTasks
description: 'Clean old IP tracking data from nuxt-api-shield storage.',
},
async run() {
const shieldStorage = useStorage('shield');
const config = useRuntimeConfig().public.nuxtApiShield;
// ipTTL is expected to be in seconds from config (module applies default if not set by user)
const ipTTLseconds = config.ipTTL;
if (!ipTTLseconds || ipTTLseconds <= 0) {
console.log('[nuxt-api-shield] IP data cleanup (ipTTL) is disabled or invalid.');
return { result: { cleanedCount: 0, status: 'disabled_or_invalid_ttl' } };
}
const ipTTLms = ipTTLseconds * 1000;
const ipKeys = await shieldStorage.getKeys('ip:');
const currentTime = Date.now();
let cleanedCount = 0;
for (const key of ipKeys) {
const entry = await shieldStorage.getItem(key) as RateLimit | null;
// Check if entry exists and has a numeric 'time' property
if (entry && typeof entry.time === 'number') {
if ((currentTime - entry.time) > ipTTLms) {
await shieldStorage.removeItem(key);
cleanedCount++;
}
} else {
// Clean up entries that are null, not an object, or missing a numeric 'time'
await shieldStorage.removeItem(key);
cleanedCount++;
}
}
console.log(`[nuxt-api-shield] Cleaned ${cleanedCount} old/malformed IP data entries.`);
return { result: { cleanedCount } };
},
});
如果您希望使用与默认值(7 天)不同的值,请确保在 nuxt.config.ts 中的 nuxtApiShield 下配置 ipTTL。在您的配置中将 ipTTL: 0(或任何非正数)设置为禁用此清理任务。如果您的模块导出 RateLimit 类型或使其可用于 Nuxt 的自动导入系统,则该类型应该通过 #imports 可用。
nuxt-api-shield 通过跟踪 IP 地址来监控请求速率和应用禁用。这意味着 IP 地址,根据 GDPR 等法规可被视为个人可识别信息 (PII),由模块存储。
ip:<IP_ADDRESS>:存储 { count: number, time: number } 以跟踪请求速率。ban:<IP_ADDRESS>:存储一个时间戳,指示 IP 地址禁用的到期时间。shield:cleanBans 任务清理。ipTTL 设置由 shield:cleanIpData 任务清理。driver: 'fs'):** 如果您对 unstorage 使用文件系统驱动程序(例如,driver: 'fs', base: '.shield'),请确保存储目录(以及如果通过 log.path 启用了日志记录的 logs 目录)errorMessage)模块配置中的 errorMessage 选项在 429 响应的正文中返回。
errorMessage 中使用 HTML,请确保您的客户端应用程序正确地对其进行清理或以防止 XSS 漏洞的方式呈现。模块本身不会清理此用户配置的消息。# Install dependencies
yarn
# Generate type stubs
yarn dev:prepare
# Develop with the playground
yarn dev
# Build the playground
yarn dev:build
# Run ESLint
yarn lint
# Run Vitest
yarn test
yarn test:watch
# Release new version
yarn release:patch
yarn release:minor