nestjs使用redis實現(xiàn)ip限流的步驟詳解
導讀
如果使用nestjs開發(fā)接口并部署之后,我們通常需要考慮到接口是否會被惡意盜刷消耗過多的資源,一個簡單的方式就是限制在單位時間內(nèi)的訪問次數(shù)。
本文使用的庫包版本如下:
庫名 | 版本號 |
---|---|
@nestjs/core | 10.0.0 |
@nestjs/common | 10.0.0 |
@nestjs/schedule | 4.1.2 |
ioredis | 5.4.2 |
本文的主要工作環(huán)境基于Macbook Pro M1 MacOS 14.6.1。
新建nestjs 項目
nest new nestjs-with-ip-limit -g
nestjs中的守衛(wèi)Guard
nestjs 提供了一種可以是否攔截請求的方式,守衛(wèi)(Guard),我們可以通過實現(xiàn)CanActive
接口來完成,詳細解釋參考官方鏈接。
自定義的一個ip.guard.ts
文件,用于最終實現(xiàn)我們的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);
可以看到,一旦守衛(wèi)中返回了false,請求將報403請求錯誤。
Guard中獲取IP
現(xiàn)在的問題就是如何在實現(xiàn)的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-for
和x-real-ip
的依據(jù)主要是我們很多網(wǎng)站可能使用代理的方式運行,尤其是nginx
代理,如下所示。
location ^~ /api { rewrite ^/api(.*) $1 break; # 重寫規(guī)則,將/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 地址非常重要,特別是在請求經(jīng)過多個代理的情況下 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地址后我們需要將其和請求數(shù)保存,并同時記錄訪問數(shù)(每次增加1),且在某段時間后清除,為此,我們需要引入redis。
npm i ioreds -s
為了后續(xù)更方便的使用,把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的訪問次數(shù),如果達到五次后,就返回false禁止接口應答,否則通過,并且該限制在一分鐘內(nèi)有效。
在瀏覽器請求http://localhost:3000,刷新四次后,顯示如下。::1
是由于本地開發(fā)的緣故,如果有服務器可以在服務器上啟動服務,本地測試。
部署到服務器后顯示:
補充
現(xiàn)在經(jīng)常使用的一些AI工具,其免費計劃每天都只有很少的額度,其也可以基于redis實現(xiàn)限流,不過是根據(jù)用戶id來設置key值。除此之外,其每天到零點時還可以恢復額度。為此,可以在nestjs使用定時器在零點時刪除所有的redis的ke y。
安裝相關(guān)依賴
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_*'); } }
此外,也可以在定時任務中將相關(guān)的限流ip的計數(shù)同步到MySQL
,讓相關(guān)邏輯更穩(wěn)檔一些。
以上就是nestjs使用redis實現(xiàn)ip限流的步驟詳解的詳細內(nèi)容,更多關(guān)于nestjs redis實現(xiàn)ip限流的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
redis-cli創(chuàng)建redis集群的實現(xiàn)
本文主要介紹了redis-cli創(chuàng)建redis集群的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-06-06Redis遍歷所有key的兩個命令(KEYS 和 SCAN)
這篇文章主要介紹了Redis遍歷所有key的兩個命令(KEYS 和 SCAN),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04