当前位置: 代码网 > it编程>编程语言>Javascript > nestjs使用redis实现ip限流的步骤详解

nestjs使用redis实现ip限流的步骤详解

2025年01月16日 Javascript 我要评论
导读如果使用nestjs开发接口并部署之后,我们通常需要考虑到接口是否会被恶意盗刷消耗过多的资源,一个简单的方式就是限制在单位时间内的访问次数。本文使用的库包版本如下:库名版本号@nestjs/cor

导读

如果使用nestjs开发接口并部署之后,我们通常需要考虑到接口是否会被恶意盗刷消耗过多的资源,一个简单的方式就是限制在单位时间内的访问次数。

本文使用的库包版本如下:

库名版本号
@nestjs/core10.0.0
@nestjs/common10.0.0
@nestjs/schedule4.1.2
ioredis5.4.2

本文的主要工作环境基于macbook pro m1 macos 14.6.1

新建nestjs 项目

nest new nestjs-with-ip-limit -g

nestjs中的守卫guard

nestjs 提供了一种可以是否拦截请求的方式,守卫(guard),我们可以通过实现canactive接口来完成,详细解释参考官方链接

自定义的一个ip.guard.ts文件,用于最终实现我们的ip请求拦截。

//ip.guard.ts
import { injectable, canactivate, executioncontext } from '@nestjs/common';
import { observable } from 'rxjs';
​
@injectable()
export class ipguard implements canactivate {
  canactivate(
    context: executioncontext,
  ): boolean | promise<boolean> | observable<boolean> {
    const request = context.switchtohttp().getrequest();
    console.log(request.headers['origin'], request.headers);
    return request.headers['text'] != 'zoo' ? false : true;
  }
}

在示例中,我们增加当请求头没有text=zoo就拦截的逻辑,并直接在浏览器控制台中使用fetch测试:

fetch('http://localhost:3000', {
  headers: {
    text: 'zoo',
  },
})
  .then((resp) => resp.text())
  .then(console.log)
  .catch(console.error);

可以看到,一旦守卫中返回了false,请求将报403请求错误。

guard中获取ip

现在的问题就是如何在实现的ipguard中获取ip地址,可以通过context.switchtohttp().getrequest()获取请求对象来提取。

const request = context.switchtohttp().getrequest();
const ip = request.headers['x-forwarded-for'] || request.headers['x-real-ip'] || request.socket.remoteaddress || request.ip;

x-forwarded-forx-real-ip的依据主要是我们很多网站可能使用代理的方式运行,尤其是nginx代理,如下所示。

location ^~ /api {
    rewrite ^/api(.*) $1 break; # 重写规则,将/api之后的路径提取出来并去掉/api前缀
    proxy_pass http://127.0.0.1:6689; 
    proxy_set_header host $host; 
    proxy_set_header x-real-ip $remote_addr; // 设置 x-real-ip 头为客户端的真实 ip 地址。这对于后端服务识别客户端 ip 地址非常重要,特别是在请求经过多个代理的情况下
    proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for; // 设置 x-forwarded-for 头为通过 proxy_add_x_forwarded_for 指令添加的信息。此头通常用于跟踪客户端 ip 地址以及任何之前的代理 ip 地址
    proxy_set_header remote-host $remote_addr; 
    proxy_set_header upgrade $http_upgrade; 
    proxy_set_header connection "upgrade"; 
    proxy_set_header x-forwarded-proto $scheme; 
    proxy_http_version 1.1; 
    add_header x-cache $upstream_cache_status; 
    add_header cache-control no-cache; 
    proxy_ssl_server_name off; 
}

ip存储

提取到ip地址后我们需要将其和请求数保存,并同时记录访问数(每次增加1),且在某段时间后清除,为此,我们需要引入redis。

npm i ioreds -s

为了后续更方便的使用,把redis封装为一个自建的module

nest g module redis --no-spec

新建src/redis/redis.service.ts

import { injectable } from '@nestjs/common';
​
import client, { type redisoptions } from 'ioredis';
​
@injectable()
export class redisservice extends client {
  constructor(options: redisoptions) {
    super(options);
  }
}

redis.module.ts中加入代码

import { module } from '@nestjs/common';
import { redisoptions } from 'ioredis';
import { redisservice } from './redis.service';

@module({})
export class redismodule {
  static forroot(optionts: redisoptions) {
    return {
      module: redismodule,
      providers: [
        {
          provide: 'redis_options',
          usevalue: optionts,
        },
        {
          provide: redisservice,
          usefactory: (options: redisoptions) => {
            return new redisservice(options);
          },
          inject: ['redis_options'],
        },
      ],
      exports: [redisservice],
    };
  }
}

在app.module.ts中使用

新建一个redis容器:

随后改造ip.guard.ts文件

import { injectable, canactivate, executioncontext } from '@nestjs/common';
import { redisservice } from './redis/redis.service';

@injectable()
export class ipguard implements canactivate {
  constructor(private redisservice: redisservice) {}
  async canactivate(context: executioncontext): promise<boolean> {
    const request = context.switchtohttp().getrequest();
    const ip =
      request.headers['x-forwarded-for'] ||
      request.headers['x-real-ip'] ||
      request.socket.remoteaddress ||
      request.ip;
    const redis_key = 'limit_ip_' + ip;
    const data = await this.redisservice.get(redis_key);
    const count = data ? parseint(data) : 0;
    if (count >= 5) {
      return false;
    }
    await this.redisservice.set(
      redis_key,
      data ? parseint(data) + 1 : 1,
      'ex',
      60,
    );
    return true;
  }
}

每次接口访问时,都会先从redis里读取对应ip的访问次数,如果达到五次后,就返回false禁止接口应答,否则通过,并且该限制在一分钟内有效。

在浏览器请求http://localhost:3000,刷新四次后,显示如下。::1是由于本地开发的缘故,如果有服务器可以在服务器上启动服务,本地测试。

部署到服务器后显示:

补充

现在经常使用的一些ai工具,其免费计划每天都只有很少的额度,其也可以基于redis实现限流,不过是根据用户id来设置key值。除此之外,其每天到零点时还可以恢复额度。为此,可以在nestjs使用定时器在零点时删除所有的redis的ke y。

安装相关依赖

npm install @nestjs/schedule

注册定时任务模块

imports: [
    redismodule.forroot({
      host: 'localhost',
      port: 6378,
      db: 0,
    }),
    schedulemodule.forroot(),
  ],

app.service.ts加入代码

import { injectable } from '@nestjs/common';
import { cron, cronexpression } from '@nestjs/schedule';
import { redisservice } from './redis/redis.service';

@injectable()
export class appservice {
  constructor(private readonly redisservice: redisservice) {}
  gethello(): string {
    return 'hello world!';
  }

  @cron(cronexpression.every_day_at_1am)
  async handlecron() {
    console.log('called when the current time is 1am');
    //删除所有的redis keys: limit_ip_*
    await this.redisservice.del('limit_ip_*');
  }
}

此外,也可以在定时任务中将相关的限流ip的计数同步到mysql,让相关逻辑更稳档一些。

以上就是nestjs使用redis实现ip限流的步骤详解的详细内容,更多关于nestjs redis实现ip限流的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com