速率限制
速率限制
一种常见的技术是保护应用程序免受暴力攻击,即速率限制。首先,您需要安装@nestjs/throttler包。
$ npm i --save @nestjs/throttler
安装完成后,ThrottlerModule可以配置为任何其他Nest包,使用forRoot或forRootAsync方法。
@Module({
imports: [
ThrottlerModule.forRoot([
{
name: 'short',
ttl: 1000,
limit: 3,
},
{
name: 'medium',
ttl: 10000,
limit: 20
},
{
name: 'long',
ttl: 60000,
limit: 100
}
]),
],
})
export class AppModule {}
上述配置将为您的应用程序设置全局选项,这些选项将用作每个节流器的默认值。每个节流器都有以下设置:
name:节流器的名称。如果没有提供名称,它将默认为defaultttl:每个请求在存储中持续的毫秒数limit:TTL限制内的最大请求数ignoreUserAgents:要忽略的用户代理正则表达式数组skipIf:返回布尔值的函数,指示是否应跳过节流器
提示 ttl以毫秒为单位。
一旦模块被导入,您可以选择如何绑定ThrottlerGuard。守卫章节中提到的任何现有守卫绑定技术都适用。例如,如果您想要全局绑定守卫,您可以通过将此提供者添加到任何模块来实现:
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
}
自定义
可能有时您想要绑定守卫,但想要禁用某些路由的速率限制。为此,您可以使用@SkipThrottle()装饰器,传递一个布尔值来表示是否应跳过此路由的节流。
@SkipThrottle()
@Get()
dontThrottle() {
return "Throttling skipped for this route";
}
您还可以传递一个对象来跳过特定的节流器:
@SkipThrottle({ default: false, short: true, medium: true, long: false })
@Get()
dontThrottle() {
return "Throttling skipped for this route";
}
这将跳过short和medium节流器,但不会跳过default和long节流器。如果您有一个全局守卫,但想要禁用某些路由的节流,这将非常有用。
@SkipThrottle()装饰器也可以用来跳过整个类或跳过特定的节流器。如果您将@SkipThrottle()装饰器应用于类,您可以通过将@Throttle()装饰器应用于处理程序来覆盖该行为。
还有@Throttle()装饰器,可以用来覆盖limit和ttl设置,遵循与上面设置的相同接口。此装饰器可以用于装饰类或函数。使用此装饰器时,您可以传递一个对象,其中键是节流器名称,值是具有limit和/或ttl键的对象,或者您可以传递一个数字,这将用作默认节流器的限制。重要的是要注意,此装饰器将覆盖模块级别设置,而不是与之合并。例如:
// 覆盖默认配置。
@Throttle({ default: { limit: 3, ttl: 60000 } })
@Get()
findAll() {
return "List users";
}
// 覆盖默认限制。
@Throttle(5)
@Get()
findAll() {
return "List users";
}
// 覆盖短期和长期配置,但保持中期配置。
@Throttle({ short: { limit: 1 }, long: { limit: 10 } })
@Get()
findAll() {
return "List users";
}
代理
如果您的应用程序在代理服务器后面运行,请检查特定的HTTP适配器选项(express和fastify),以获取trust proxy选项,并启用它。这样做将允许您从X-Forwarded-For头获取原始IP地址,并且您可以覆盖getTracker()方法来从头而不是从req.ip中提取值。以下示例适用于express和fastify:
// throttler-behind-proxy.guard.ts
import { ThrottlerGuard } from '@nestjs/throttler';
import { Injectable } from '@nestjs/common';
@Injectable()
export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
protected async getTracker(req: Record<string, any>): Promise<string> {
return req.ips.length ? req.ips[0] : req.ip; // 个性化IP提取以满足您的需求
}
}
WebSockets
此模块可以与websockets一起使用,但需要一些类扩展。您可以扩展ThrottlerGuard并覆盖handleRequest方法,如下所示:
@Injectable()
export class WsThrottlerGuard extends ThrottlerGuard {
async handleRequest(context: ExecutionContext, limit: number, ttl: number): Promise<boolean> {
const client = context.switchToWs().getClient();
const ip = client.conn.remoteAddress;
const key = this.generateKey(context, ip);
const { totalHits } = await this.storageService.increment(key, ttl);
if (totalHits > limit) {
throw new ThrottlerException();
}
return true;
}
}
提示 如果您使用ws库,客户端的IP可以在client._socket.remoteAddress找到。
GraphQL
ThrottlerGuard也可以与GraphQL请求一起使用。同样,守卫可以被扩展,但这次getRequestResponse方法将被覆盖
@Injectable()
export class GqlThrottlerGuard extends ThrottlerGuard {
getRequestResponse(context: ExecutionContext) {
const gqlCtx = GqlExecutionContext.create(context);
const ctx = gqlCtx.getContext();
return { req: ctx.req, res: ctx.res };
}
}
配置
以下选项对ThrottlerModule有效:
name |
节流器的名称 |
ttl |
每个请求在存储中持续的毫秒数 |
limit |
TTL限制内的最大请求数 |
ignoreUserAgents |
要忽略的用户代理正则表达式数组 |
skipIf |
返回布尔值的函数,指示是否应跳过节流器 |
storage |
存储服务,用于跟踪请求 |
异步配置
您可能希望异步获取节流器选项,而不是静态传递它们。在这种情况下,使用forRootAsync()方法,它提供了几种处理异步配置的方法。
一种方法是使用工厂函数:
ThrottlerModule.forRootAsync({
useFactory: () => [
{
ttl: 60000,
limit: 10,
},
],
})
我们的工厂的行为与任何其他异步提供者一样(例如,它可以是async的,并且能够通过inject注入依赖项)。
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => [
{
ttl: configService.get('THROTTLE_TTL'),
limit: configService.get('THROTTLE_LIMIT'),
},
],
inject: [ConfigService],
})
或者,您可以使用useClass语法:
ThrottlerModule.forRootAsync({
useClass: ThrottlerConfigService,
})
上述构造将在ThrottlerModule内部实例化ThrottlerConfigService,并使用它来获取选项对象。ThrottlerConfigService必须实现ThrottlerOptionsFactory接口。
@Injectable()
class ThrottlerConfigService implements ThrottlerOptionsFactory {
createThrottlerOptions(): ThrottlerModuleOptions {
return [
{
ttl: 60000,
limit: 10,
},
];
}
}
如果您希望重用现有的配置提供者而不是在ThrottlerModule内部创建ThrottlerConfigService的私有副本,请使用useExisting语法。
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
useExisting: ConfigService,
})
这与useClass的工作方式相同,但有一个关键区别——ThrottlerModule将查找导入的模块以重用现有的ConfigService,而不是实例化新的。
存储
内置存储是内存缓存,用于跟踪请求。当您的应用程序在多个集群中运行时,您将遇到问题,因为每个集群都有自己独立的内存。为了使用一致的存储,您可以创建自己的存储服务,该服务实现ThrottlerStorage接口,或者您可以使用社区存储提供者。
Redis
社区存储提供者nestjs-throttler-storage-redis可用于Redis存储。
首先安装包:
$ npm i nestjs-throttler-storage-redis
然后实现存储:
import { ThrottlerModule } from '@nestjs/throttler';
import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis';
@Module({
imports: [
ThrottlerModule.forRoot({
throttlers: [
{
ttl: 60000,
limit: 10,
},
],
storage: new ThrottlerStorageRedisService(),
}),
],
})
export class AppModule {}
有关此包的更多选项,请参阅其文档。