iT邦幫忙

2021 iThome 鐵人賽

DAY 6
1
Security

從以卵擊石到堅若磐石之 Web API 安全性全攻略系列 第 6

Day06-流量限制(一)

前言

不管你的伺服器有多少 CPU、多大量的記憶體,每秒可以處理的請求數終究是有限的。為了避免資源被少數惡意使用者用完,造成阻斷服務攻擊(Denial Of Service),一定要限制每個使用者能夠使用的資源量。

而最常見的資源限制方式,就是做限流(rate limiting),當一個使用者無法再發請求給伺服器時,自然就沒辦法再消耗伺服器上的資源

整個 Application Server 的限流

如果你架設的 API 服務除了用 Node.js/Go 寫成的 application server 之外,前面沒有擋任何 reverse proxy、load balancer,那最簡單的方式就是用 express/gin 的 middleware 來做限流了

在 Node.js 裡,有一個用來做限流的 express middleware 叫做 express-rate-limit,如果我想限制每個 IP 在十分鐘內最多只能發 1000 個請求到 server,那只要這樣寫就可以了

const rateLimit = require("express-rate-limit");

const globalLimiter = rateLimit({
    windowMs: 10 * 60 * 1000, // 10 分鐘
    max: 1000
});

app.use(globalLimiter)

app.get('/api/user', getUserHandler)
app.post('/api/user', createUserHandler)
// ...

如此一來,只要有人嘗試在十分鐘內發送超過 1000 個請求給 API Server,就會馬上收到 429 Too Many Requests

而 Go 的話也有一個 Gin 的 middleware 叫做 limiter,用起來稍微複雜一點,但基本概念就是設定好時間跟次數後,把 middleware 套用上去就可以了

package main

import (
    "github.com/ulule/limiter/v3"
    "github.com/ulule/limiter/v3/drivers/store/memory"  
    mgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
    "github.com/gin-gonic/gin"
)

func main() {
    // 每十分鐘最多 1000 個請求
    rate := limiter.Rate{
        Period: 10 * time.Minute,
        Limit:  1000,
    }
	
    // 把資料存在記憶體裡面
    store := memory.NewStore()
    middleware := mgin.NewMiddleware(limiter.New(store, rate))

    router := gin.Default()

    // 套用限流的 middleware
    router.Use(middleware)	
    router.GET("/api/user", getUserHandler)
    router.POST("/api/user", createUserHandler)
	
    router.Run(":8888")
}

針對不同的 endpoint 做限流

一般來說根據每個 API endpoint 要做的事情不同,所需要的資源(CPU、時間、$$$)也會不同,所以像上面的範例只有規定「每十分鐘內最多一千個請求」並不是個好方法,應該要針對高成本的 endpoint 做更嚴格的限制

舉個比較極端的例子,譬如說你有一個 API endpoint POST /api/verify/{phoneNumber} 是用來發送認證簡訊的,而每發一封簡訊需要 0.5 元。所以如果攻擊者在十分鐘內連續送出 1000 個發送簡訊的請求,就算你的 server 頂得住流量,也會讓你每十分鐘就浪費 500 元(一天就噴 72000),可能公司還沒開始賺錢就倒了XD

所以為了防止這種情況,應該要針對特別浪費資源(不管是運算資源還是 $$$)的 api 做特別的限制,才不會被攻擊者找到弱點後直接被打掛

const rateLimit = require("express-rate-limit");

const globalLimiter = rateLimit({
    windowMs: 10 * 60 * 1000,
    max: 1000
});

// 每個小時最多發 5 次簡訊
const verifyPhoneLimiter = rateLimit({
    windowMs: 60 * 60 * 1000,
    max: 5
});

app.use(globalLimiter)

app.get('/api/user', getUserHandler)
app.post('/api/verify/:phoneNumber', verifyPhoneLimiter, verifyPhoneHandler)

小結

今天講了怎麼在 API server 裡面自己用 middleware 做限流,雖然這樣做感覺很方便,但也會讓 API server 無法完全專注在業務邏輯上,所以明天要來說說如果在 API server 前面有擋 nginx 之類的 load balancer 的話,要怎麼在上面設定限流以減低 API server 的負擔

如果對於今天的內容有什麼問題的話歡迎在下方留言跳出來,沒有的話那我們就明天見囉~


上一篇
Day05-入口管制(四)
下一篇
Day07-流量限制(二)
系列文
從以卵擊石到堅若磐石之 Web API 安全性全攻略30

尚未有邦友留言

立即登入留言