如何在NestJS中使用Redis实现IP速率限制以及请求体速率限制?

huangapple go评论59阅读模式
英文:

How to achieve IP Rate Limiting along with Body Request Rate Limiting in NestJS With Redis?

问题

I was wondering if we can achieve Rate Limiting of IP and Request Body (same some username) separately (Either one if fulfilled should give me Error 429) for same controller function (Route).

Tried Using following packages -

"nestjs-throttler-storage-redis": "^0.3.0"
"@nestjs/throttler": "^4.0.0",
"ioredis": "^5.3.2"

app.module.ts -

ThrottlerModule.forRoot({
  ttl: process.env.IP_VELOCITY_TTL as unknown as number, // 24 hours in seconds
  limit: process.env.IP_VELOCITY_COUNT as unknown as number, // X number requests per ttl per key (IP address in this case)
  storage: new ThrottlerStorageRedisService(new Redis()),
}),

In Respective Module.ts -

{
  provide: APP_GUARD,
  useClass: ThrottlerGuard,
},

controller.ts -

@Throttle(3, 60 * 60)

But this is not sufficient as this is blocking all the requests post 3 times!

Can anybody suggest me to achieve this in Right way ?

英文:

I was wondering if we can achieve Rate Limiting of IP and Request Body (same some username) separately (Either one if fulfilled should give me Error 429) for same controller function (Route).

Tried Using following packages -

"nestjs-throttler-storage-redis": "^0.3.0"
"@nestjs/throttler": "^4.0.0",
"ioredis": "^5.3.2"

app.module.ts -

ThrottlerModule.forRoot({
  ttl: process.env.IP_VELOCITY_TTL as unknown as number, // 24 hours in seconds
  limit: process.env.IP_VELOCITY_COUNT as unknown as number, // X number requests per ttl per key (IP address in this case)
  storage: new ThrottlerStorageRedisService(new Redis()),
}),

In Respective Module.ts -

{
  provide: APP_GUARD,
  useClass: ThrottlerGuard,
},

controller.ts -

@Throttle(3, 60 * 60)

But this is not sufficient as this is blocking all the requests post 3 times!

Can anybody suggest me to achieve this in Right way ?

答案1

得分: 1

以下是翻译好的代码部分:

// 技巧是覆盖`ThrottlerGuard`类,如下所示 -

import { ExecutionContext, Injectable } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';

@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {
  // 覆盖以处理IP限制以及firstName + lastName限制
  async handleRequest(
    context: ExecutionContext,
    limit: number,
    ttl: number
  ): Promise<boolean> {
    
    const { req, res } = this.getRequestResponse(context);

    // 如果应忽略当前用户代理,请提前返回。
    if (Array.isArray(this.options.ignoreUserAgents)) {
      for (const pattern of this.options.ignoreUserAgents) {
        if (pattern.test(req.headers['user-agent'])) {
          return true;
        }
      }
    }

    // IP跟踪器
    const tracker = this.getTracker(req);
    const key = this.generateKey(context, tracker);
    const { totalHits, timeToExpire } = await this.storageService.increment(
      key,
      ttl
    );

    // firstName和lastName的跟踪器
    const firstNameAndLastNameTracker = this.getNameTracker(req);
    const nameKey = this.generateKey(context, firstNameAndLastNameTracker);
    const { totalHits: totalHitsName, timeToExpire: timeToExpireName } =
      await this.storageService.increment(nameKey, ttl);

    // 当用户达到其限制(IP)时抛出错误。
    if (totalHits > limit) {
      res.header('Retry-After', timeToExpire);
      this.throwThrottlingException(context);
    }

    // 当用户达到其firstName + lastName限制时抛出错误。
    if (
      totalHitsName > parseInt(process.env.FIRSTNAME_LASTNAME_MAX_TRY_COUNT)
    ) {
      res.header('Retry-After', timeToExpireName);
      this.throwThrottlingException(context);
    }

    res.header(`${this.headerPrefix}-Limit`, limit);
    // 我们即将添加一条记录,所以我们需要在这里考虑这一点。
    // 否则,标头会说我们还有一个请求,当没有请求时。
    res.header(
      `${this.headerPrefix}-Remaining`,
      Math.max(0, limit - totalHits)
    );
    res.header(`${this.headerPrefix}-Reset`, timeToExpire);

    return true;
  }

  protected getNameTracker(req: Record<string, any>): string {
    return req.body.firstName + req.body.lastName;
  }
}

请注意,我只翻译了代码部分,没有包括注释或其他内容。

英文:

The trick was to overwrite ThrottlerGuard Class like below -

import { ExecutionContext, Injectable } from &#39;@nestjs/common&#39;;
import { ThrottlerGuard } from &#39;@nestjs/throttler&#39;;
@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {
// Overwritten to handle the IP restriction along with firstName + lastName restriction
async handleRequest(
context: ExecutionContext,
limit: number,
ttl: number
): Promise&lt;boolean&gt; {
const { req, res } = this.getRequestResponse(context);
// Return early if the current user agent should be ignored.
if (Array.isArray(this.options.ignoreUserAgents)) {
for (const pattern of this.options.ignoreUserAgents) {
if (pattern.test(req.headers[&#39;user-agent&#39;])) {
return true;
}
}
}
// Tracker for IP
const tracker = this.getTracker(req);
const key = this.generateKey(context, tracker);
const { totalHits, timeToExpire } = await this.storageService.increment(
key,
ttl
);
// Tracker for firstName and lastName
const firstNameAndLastNameTracker = this.getNameTracker(req);
const nameKey = this.generateKey(context, firstNameAndLastNameTracker);
const { totalHits: totalHitsName, timeToExpire: timeToExpireName } =
await this.storageService.increment(nameKey, ttl);
// Throw an error when the user reached their limit (IP).
if (totalHits &gt; limit) {
res.header(&#39;Retry-After&#39;, timeToExpire);
this.throwThrottlingException(context);
}
// Throw an Error when user reached their firstName + lastName Limit.
if (
totalHitsName &gt; parseInt(process.env.FIRSTNAME_LASTNAME_MAX_TRY_COUNT)
) {
res.header(&#39;Retry-After&#39;, timeToExpireName);
this.throwThrottlingException(context);
}
res.header(`${this.headerPrefix}-Limit`, limit);
// We&#39;re about to add a record so we need to take that into account here.
// Otherwise the header says we have a request left when there are none.
res.header(
`${this.headerPrefix}-Remaining`,
Math.max(0, limit - totalHits)
);
res.header(`${this.headerPrefix}-Reset`, timeToExpire);
return true;
}
protected getNameTracker(req: Record&lt;string, any&gt;): string {
return req.body.firstName + req.body.lastName;
}
}

答案2

得分: 0

You'll need to create your own guard that extends ThrottlerGuard and overrides the getTracker method so that it returns this ip and req.body.username combo. Something like

@Injectable()
export class ThrottleIpBodyGuard extends ThrottlerGuard {

  getTracker(req: Request) {
    return req.ip + req.body.username;
  }
}

Then, instead of useClass: ThrottlerGuard you can use useClass: ThrottleIpBodyGuard

英文:

You'll need to create your own guard that extends ThrottlerGuard and overrides the getTracker method so that it returns this ip and req.body.username combo. Something like

@Injectable()
export class ThrottleIpBodyGuard extends ThrottlerGuard {

  getTracker(req: Request) {
    return req.ip + req.body.username;
  }
}

Then, instead of useClass: ThrottlerGuard you can use useClass: ThrottleIpBodyGuard

huangapple
  • 本文由 发表于 2023年5月10日 20:35:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76218496.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定