做过后端研发的基本对接口限频完全不陌生,特别是针对一些核心接口受到攻击的时候,比如 Jmeter 来通过一些用户填写接入恶意灌入脏数据。
那在 nodejs 这边如何设计 限频接口呢?
基于 express 的 express-rate-limit
源码地址:
https://github.com/nfriedly/express-rate-limit
<span class="code-snippet_outer">Basic rate-limiting middleware <span class="code-snippet__keyword">for 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>
其实这个包不老,都是 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">  increment: <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<incrementresponse> | 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">  resetTime!: 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">允许传一些配置对象给 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">  <span class="code-snippet__keyword">delete <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/
转载文章受原作者版权保护。转载请注明原作者出处!