在 Web 應用中,通常會需要對 IP 訪問進行限制以及控制提交次數,以防止惡意攻擊(例如暴力破解、DoS攻擊、API濫用等)。爲了實現這一功能,我們可以結合 Golang 的特性,使用中介軟體或者基於 Redis 這樣的快取服務來實現 IP 限制和提交次數的控制。
實現步驟
IP 訪問限制:對每個 IP 的訪問頻次進行限制,比如每個 IP 每分鐘只能訪問某個介面 10 次。超過限制後,返回錯誤資訊(例如 429 Too Many Requests)。
提交次數限制:透過限制某個時間段內某個 IP 的提交次數,防止暴力破解或者濫用介面。
Redis(或其他儲存系統)作為計數器:爲了更好地實現這種限制,可以使用 Redis 等快取系統來儲存 IP 的訪問記錄、提交次數等,因為 Redis 的效能和易用性使它成為理想的選擇。
核心概念
Rate Limiting(限流):根據 IP 限制某個時間段內的訪問次數。
請求次數計數:對每個 IP 進行計數,並基於計數來判斷是否超過限制。
時間視窗:設定一定的時間視窗(例如一分鐘或五分鐘),在這個時間段內統計 IP 的訪問次數。
使用 Golang 及 Redis 實現 IP 訪問限制和提交次數限制
這裏我們使用 Redis 來儲存和控制訪問次數,並結合 Go 實現一個簡單的 IP 訪問限制中介軟體。
依賴庫
你可以使用 Redis 官方的 Go 客戶端 go-redis 來連線 Redis 進行操作。先安裝這個庫:
go get github.com/go-redis/redis/v8
實現程式碼
下面的程式碼演示瞭如何使用 Redis 來實現 IP 訪問限制和提交次數限制。
package main import ( "context" "fmt" "log" "net/http" "strconv" "time" "github.com/go-redis/redis/v8" ) // Redis client var rdb *redis.Client // 初始化 Redis 客戶端 func initRedis() { rdb = redis.NewClient(&redis.Options{ Addr: "localhost:6379", // Redis 地址 Password: "", // Redis 密碼(如果有) DB: 0, // 使用的 Redis 資料庫 }) } // 獲取客戶端的 IP 地址 func getIP(r *http.Request) string { // 嘗試從 X-Forwarded-For 或 X-Real-IP 獲取真實 IP ip := r.Header.Get("X-Forwarded-For") if ip == "" { ip = r.Header.Get("X-Real-IP") } if ip == "" { ip = r.RemoteAddr } return ip } // 中介軟體:IP 訪問限制 func rateLimitMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := context.Background() ip := getIP(r) key := "rate_limit:" + ip // 獲取 Redis 中的訪問次數 count, err := rdb.Get(ctx, key).Result() if err == redis.Nil { // 如果沒有記錄,設定計數為1,並設定過期時間 err := rdb.Set(ctx, key, 1, time.Minute).Err() // 1 分鐘限制 if err != nil { http.Error(w, "Redis error", http.StatusInternalServerError) return } } else if err != nil { http.Error(w, "Redis error", http.StatusInternalServerError) return } else { // 將訪問次數轉換為整數 countInt, _ := strconv.Atoi(count) if countInt >= 10 { // 假設限制為每分鐘最多10次 http.Error(w, "Too Many Requests", http.StatusTooManyRequests) return } // 遞增計數 rdb.Incr(ctx, key) } next.ServeHTTP(w, r) } } // 示例處理器:提交處理 func submitHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Request successful") } func main() { // 初始化 Redis initRedis() // 建立 HTTP 伺服器並新增中介軟體 http.HandleFunc("/submit", rateLimitMiddleware(submitHandler)) log.Println("Server is running on port 8080...") http.ListenAndServe(":8080", nil) }
程式碼解析
Redis 客戶端初始化:
使用
redis.NewClient()
初始化 Redis 客戶端。透過
rdb.Set()
和rdb.Get()
來操作 Redis 中的計數器。IP 獲取:
透過
getIP()
函式獲取請求的客戶端 IP 地址。該函式嘗試從請求頭中的X-Forwarded-For
或X-Real-IP
獲取真實的 IP。如果沒有,則使用RemoteAddr
。Rate Limiting 中介軟體:
rateLimitMiddleware()
是核心的中介軟體函式,負責限制每個 IP 的訪問次數。它使用 Redis 來儲存每個 IP 的訪問計數和限流時間視窗(這裏設定為 1 分鐘)。當 IP 的訪問次數超過限制時,返回 HTTP 狀態碼
429 Too Many Requests
。處理請求:
submitHandler()
是一個簡單的示例處理器,處理成功的請求。訪問
/submit
時,經過中介軟體限制後,正常情況下返回 "Request successful"。
改進與擴充套件
動態調整限流策略: 可以根據不同的使用者型別、不同的 API 路徑動態調整限流策略。例如,VIP 使用者可能會有更高的訪問頻次。
IP 黑名單: 透過 Redis 或其他儲存系統維護一個黑名單,遇到黑名單中的 IP 可以直接拒絕請求。
按時間視窗的限流演算法: 你可以採用滑動視窗、漏桶演算法、令牌桶演算法等更復雜的限流演算法來實現更靈活的控制。
使用 Redis Expire 特性: 在 Redis 中使用
SetEX
(帶過期時間的鍵設定)或TTL
來確保計數器可以自動重置,避免手動管理。日誌記錄與報警: 可以結合日誌系統,在某個 IP 頻繁觸發限制時記錄日誌或傳送報警資訊。
透過 Golang 和 Redis 的結合,可以輕鬆實現 IP 訪問限制和提交次數控制。Redis 的高效能特性使其非常適合用作限流計數器的儲存。在實際應用中,可以根據需要擴充套件該方案,例如使用不同的限流演算法、結合 IP 黑名單等。