Nodejs 如何设计一个限频接口来防止攻击

做过后端研发的基本对接口限频完全不陌生,特别是针对一些核心接口受到攻击的时候,比如 Jmeter 来通过一些用户填写接入恶意灌入脏数据。

那在 nodejs 这边如何设计 限频接口呢?

基于 express 的 express-rate-limit

源码地址:

https://github.com/nfriedly/express-rate-limit

<span class="code-snippet_outer">Basic&#xA0;rate-limiting&#xA0;middleware&#xA0;<span class="code-snippet__keyword">for&#xA0;express</span></span> <span class="code-snippet_outer">Use to limit repeated requests to public APIs </span> <span class="code-snippet_outer">and/or endpoints such <span class="code-snippet__keyword">as password reset.</span></span>

Nodejs 如何设计一个限频接口来防止攻击

其实这个包不老,都是 typescript 重写的。

这里面有几个问题,因为要限制:

比如同一个 IP 在一定时间内访问的次数不能超过一定的值。

那就需要通过一个 Store 来记录,支持:

1、memory-store – 比较简单的 in-memory

然后就是依托 redis 或者 mongo,还有估计很多前端不知道的 Memcached:

2、rate-limit-redis

3、rate-limit-memcached

4、rate-limit-mongo

这个包的作者还专门写了一篇文章来描述如何自定义一个 Store,提供 typescript 和 js 的版本

https://github.com/nfriedly/express-rate-limit/wiki/Creating-Your-Own-Store

自定义存储将在内部跟踪每个标识符(如 IP地址)收到的访问次数,并随着时间的推移自动减少次数。

一个 Store 必须包含如下方法:

定义一个 Store 的 ts 类型:

<span class="code-snippet_outer"><span class="code-snippet__keyword">export <span class="code-snippet__keyword">interface Store {</span></span></span>
<span class="code-snippet_outer">  init?: <span class="code-snippet__function">(<span class="code-snippet__params">options: Options) => <span class="code-snippet__built_in">void</span></span></span></span>
<span class="code-snippet_outer">&#xA0;&#xA0;increment:&#xA0;<span class="code-snippet__function">(<span class="code-snippet__params">key:&#xA0;<span class="code-snippet__built_in">string)&#xA0;=>&#xA0;<span class="code-snippet__built_in">Promise<incrementresponse>&#xA0;|&#xA0;IncrementResponse</incrementresponse></span></span></span></span></span>
<span class="code-snippet_outer">  decrement: <span class="code-snippet__function">(<span class="code-snippet__params">key: <span class="code-snippet__built_in">string) => <span class="code-snippet__built_in">Promise<<span class="code-snippet__built_in">void> | <span class="code-snippet__built_in">void</span></span></span></span></span></span></span>
<span class="code-snippet_outer">  resetKey: <span class="code-snippet__function">(<span class="code-snippet__params">key: <span class="code-snippet__built_in">string) => <span class="code-snippet__built_in">Promise<<span class="code-snippet__built_in">void> | <span class="code-snippet__built_in">void</span></span></span></span></span></span></span>
<span class="code-snippet_outer">  resetAll?: <span class="code-snippet__function"><span class="code-snippet__params">() => <span class="code-snippet__built_in">Promise<<span class="code-snippet__built_in">void> | <span class="code-snippet__built_in">void  </span></span></span></span></span></span>
<span class="code-snippet_outer">}</span>

这里的 IncrementResponse 类型:

<span class="code-snippet_outer"><span class="code-snippet__keyword">export <span class="code-snippet__keyword">type IncrementResponse = {</span></span></span>
<span class="code-snippet_outer">  totalHits: <span class="code-snippet__built_in">number</span></span>
<span class="code-snippet_outer">  resetTime: <span class="code-snippet__built_in">Date | <span class="code-snippet__literal">undefined</span></span></span>
<span class="code-snippet_outer">}</span>

然后在 express-rate-limit 的 memory-store 设计中:

源码地址如下:

https://github.com/nfriedly/express-rate-limit/blob/master/source/memory-store.ts

源码设计:

<span class="code-snippet_outer">export <span class="code-snippet__keyword">default <span class="code-snippet__class"><span class="code-snippet__keyword">class <span class="code-snippet__title">MemoryStore <span class="code-snippet__keyword">implements <span class="code-snippet__title">Store {</span></span></span></span></span></span></span>
<span class="code-snippet_outer">  windowMs!: number</span>
<span class="code-snippet_outer">  hits!: {</span>
<span class="code-snippet_outer">    [key: string]: number | undefined</span>
<span class="code-snippet_outer">  }</span>
<span class="code-snippet_outer">&#xA0;&#xA0;resetTime!:&#xA0;Date</span>
<span class="code-snippet_outer">}</span>

这里的 windowMs:

<span class="code-snippet_outer">The duration <span class="code-snippet__keyword">of time before which all hit counts </span></span>
<span class="code-snippet_outer">are reset (<span class="code-snippet__keyword">in milliseconds)</span></span>

*、increment 方法

<span class="code-snippet_outer"><span class="code-snippet__type">It adds <span class="code-snippet__number">1 to the <span class="code-snippet__keyword">internal <span class="code-snippet__built_in">count <span class="code-snippet__keyword">for a key </span></span></span></span></span></span>
<span class="code-snippet_outer">and returns an object consisting of the new <span class="code-snippet__keyword">internal <span class="code-snippet__built_in">count (totalHits) </span></span></span>
<span class="code-snippet_outer">and the time that the <span class="code-snippet__built_in">count will reach <span class="code-snippet__number">0 (resetTime).</span></span></span>

源码设计:接受一个参数 key,进行加一

<span class="code-snippet_outer">async increment(key: string): Promise<incrementresponse> {</incrementresponse></span>
<span class="code-snippet_outer">    <span class="code-snippet__keyword">const totalHits = (<span class="code-snippet__keyword">this.hits[key] ?? <span class="code-snippet__number">0) + <span class="code-snippet__number">1</span></span></span></span></span>
<span class="code-snippet_outer">    <span class="code-snippet__keyword">this.hits[key] = totalHits</span></span>
<span class="code-snippet_outer">    <span class="code-snippet__keyword">return {</span></span>
<span class="code-snippet_outer">        totalHits,</span>
<span class="code-snippet_outer">        resetTime: <span class="code-snippet__keyword">this.resetTime,</span></span>
<span class="code-snippet_outer">    }</span>
<span class="code-snippet_outer">}</span>

*、decrement 方法

<span class="code-snippet_outer">is used only to <span class="code-snippet__string">'uncount' requests </span></span>
<span class="code-snippet_outer"><span class="code-snippet__keyword">when one <span class="code-snippet__keyword">or both of the skipSuccessfulRequests </span></span></span>
<span class="code-snippet_outer"><span class="code-snippet__keyword">or skipFailedRequests options are enabled.</span></span>

源码设计:接受一个参数 key,进行减一

<span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">async <span class="code-snippet__title">decrement(<span class="code-snippet__params">key: <span class="code-snippet__keyword">string): Promise<<span class="code-snippet__keyword">void> {</span></span></span></span></span></span></span>
<span class="code-snippet_outer">    <span class="code-snippet__keyword">const current = <span class="code-snippet__keyword">this.hits[key]</span></span></span>
<span class="code-snippet_outer">    <span class="code-snippet__keyword">if (current) {</span></span>
<span class="code-snippet_outer">      <span class="code-snippet__keyword">this.hits[key] = current - <span class="code-snippet__number">1</span></span></span>
<span class="code-snippet_outer">    }</span>
<span class="code-snippet_outer">}</span>

*、init 方法

<span class="code-snippet_outer">allows the store to <span class="code-snippet__keyword">set itself up </span></span>
<span class="code-snippet_outer"><span class="code-snippet__keyword">using the options passed <span class="code-snippet__keyword">to the middleware.</span></span></span>
<span class="code-snippet_outer">&#x5141;&#x8BB8;&#x4F20;&#x4E00;&#x4E9B;&#x914D;&#x7F6E;&#x5BF9;&#x8C61;&#x7ED9; middleware</span>

源码设计:

<span class="code-snippet_outer"><span class="code-snippet__keyword">init(options: Options): void {</span></span>
<span class="code-snippet_outer">    <span class="code-snippet__keyword">this.windowMs = options.windowMs</span></span>
<span class="code-snippet_outer">    <span class="code-snippet__keyword">this.resetTime = calculateNextResetTime(<span class="code-snippet__keyword">this.windowMs)</span></span></span>
<span class="code-snippet_outer">    <span class="code-snippet__keyword">this.hits = {}</span></span>
<span class="code-snippet_outer">    <span class="code-snippet__keyword">const interval = setInterval(async () => {</span></span>
<span class="code-snippet_outer">        await <span class="code-snippet__keyword">this.resetAll()</span></span>
<span class="code-snippet_outer">    }, <span class="code-snippet__keyword">this.windowMs)</span></span>
<span class="code-snippet_outer">    <span class="code-snippet__keyword">if (interval.unref) {</span></span>
<span class="code-snippet_outer">        interval.unref()</span>
<span class="code-snippet_outer">    }</span>
<span class="code-snippet_outer">}</span>

看下代码设计:

*、resetKey 方法

<span class="code-snippet_outer">Resets the rate limiting <span class="code-snippet__keyword">for a <span class="code-snippet__keyword">given key.</span></span></span>

源码设计:接受一个参数 key

<span class="code-snippet_outer"><span class="code-snippet__keyword">async resetKey(key: <span class="code-snippet__built_in">string): <span class="code-snippet__built_in">Promise<<span class="code-snippet__built_in">void> {</span></span></span></span></span>
<span class="code-snippet_outer">&#xA0;&#xA0;<span class="code-snippet__keyword">delete&#xA0;<span class="code-snippet__keyword">this.hits[key]</span></span></span>
<span class="code-snippet_outer">}</span>

*、resetAll 方法

reset everyone’s hit counter

源码设计:

<span class="code-snippet_outer"><span class="code-snippet__keyword">async resetAll(): <span class="code-snippet__built_in">Promise<<span class="code-snippet__keyword">void> {</span></span></span></span>
<span class="code-snippet_outer">  <span class="code-snippet__keyword">this.hits = {}</span></span>
<span class="code-snippet_outer">  <span class="code-snippet__keyword">this.resetTime = calculateNextResetTime(<span class="code-snippet__keyword">this.windowMs)</span></span></span>
<span class="code-snippet_outer">}</span>

这里核心依赖一个方法:calculateNextResetTime

<span class="code-snippet_outer"><span class="code-snippet__keyword">const calculateNextResetTime = (windowMs: <span class="code-snippet__built_in">number): <span class="code-snippet__function"><span class="code-snippet__params">Date => {</span></span></span></span></span>
<span class="code-snippet_outer">  <span class="code-snippet__keyword">const resetTime = <span class="code-snippet__keyword">new <span class="code-snippet__built_in">Date()</span></span></span></span>
<span class="code-snippet_outer">  resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs)</span>
<span class="code-snippet_outer">  <span class="code-snippet__keyword">return resetTime</span></span>
<span class="code-snippet_outer">}</span>

本篇核心从源码角度接受了如何设计限频依赖的 Store 的设计,下一篇我们继续剖析

Original: https://www.cnblogs.com/cangqinglang/p/16501042.html
Author: 苍青浪
Title: Nodejs 如何设计一个限频接口来防止攻击

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/549100/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 3.一元线性回归

    线性回归分析(Linear Regression Analysis)是确定两种或两种以上 变量间相互依赖的定量关系的一种 统计分析方法。对于一元线性回归而言,其模型主要假设为: […

    技术杂谈 2023年7月10日
    046
  • HTTP中application/x-www-form-urlencoded字符说明

    一、概述在学习ajax的时候,如果用post请求,需要设置如下代码。 ajax.setRequestHeader(“content-type”,&#8221…

    技术杂谈 2023年5月30日
    092
  • 分析自动打卡脚本——大一入学遗作

    HTTP协议 1.何为HTTP协议 HTTP协议又名超文本传输协议,是一种基于TCP/IP的传输协议,顾名思义,其传输的内容为超文本内容,在互联网早期,我们只能传输非二进制的文本,…

    技术杂谈 2023年7月11日
    070
  • 1. Two Sum——LeetCode

    Given an array of integers, return indices of the two numbers such that they add up to a s…

    技术杂谈 2023年6月21日
    092
  • FCBU喜马拉雅音频批量下载器

    如上无法下载,请点这里试试>>> 下载② 对于360杀毒软件、360卫士对本软件报病毒的严正声明!点击查看文件校验信息 ,下载文件后请对比文件校验信息,以防文件被…

    技术杂谈 2023年5月31日
    0104
  • Kubernetes Daemonset 实操笔记

    DeemonSet DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Po…

    技术杂谈 2023年7月11日
    0113
  • find 命令常用解释

    背景色是:orange #### find命令 find * path: 所有搜索的目录以及其所有子目录。默认为当前目录 * expression: 所有搜索的文件的特征 * cm…

    技术杂谈 2023年7月10日
    073
  • 阿里云与微软云的对照表

    https://docs.azure.cn/zh-cn/articles/guidance/azure-for-aliyun-service-comparison https://…

    技术杂谈 2023年5月31日
    0105
  • 软件基础的理论(1)

    软件基础的理论 一, 什么是软件产品 它是一个逻辑产品,没有实体,包括程序,文档和数据,需要通过终端设备才能体现出来功能和作用 二, 软件产品的中间过程文档 客户需求 &#…

    技术杂谈 2023年7月25日
    074
  • Kubernetes零宕机滚动更新【转】

    默认情况下,Kubernetes 的 Deployment 是具有滚动更新的策略来进行 Pod 更新的,该策略可以在任何时间点更新应用的时候保证某些实例依然可以正常运行来防止应用 …

    技术杂谈 2023年5月31日
    090
  • Python 学习路线(2022)

    原文链接: Python 学习路线(2022) 前几天整理了一份 Go 学习路线(2022),广受好评。那么趁火打劫,不是,是趁热打铁,又整理了一份 Python 学习路线。 内容…

    技术杂谈 2023年6月21日
    0124
  • 深入理解Apollo核心机制之灰度发布——创建灰度

    概述 ApolloPortal创建灰度后都做了什么呢?Apollo是如何维护主版本与灰度版本关系的呢? 其实创建灰度非常简单,可以看到下图中”Cluster&#8221…

    技术杂谈 2023年7月25日
    078
  • python 结构(序列分解)

    一、结构 结构的主要作用是将一个序列分解成若干个单独的变量。 1、对列表进行分解 2、对元组进行分解 3、对字典进行分解 4、对集合进行分解 5、对字符串进行分解 6、对文件句柄进…

    技术杂谈 2023年7月11日
    085
  • R语言_格兰因果检验

    当前文件路径 getwd() 设置当前路径,注意转译 setwd(“C://Users//Administrator//Desktop//R_test”) …

    技术杂谈 2023年7月24日
    066
  • Spring mvc源码分析系列–前言

    距离上次写文章已经过去接近两个月了,Spring mvc系列其实一直都想写,但是却不知道如何下笔,原因有如下几点: 现在项目开发前后端分离的趋势不可阻挡。Spring mvc这一套…

    技术杂谈 2023年7月25日
    075
  • [转] 腾讯云微搭低代码

    https://cloud.tencent.com/document/product/1301/48874 腾讯云微搭低代码是高效、高性能的拖拽式低代码开发平台,向上连接前端的行业…

    技术杂谈 2023年5月30日
    095
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球