|
@@ -34,7 +34,7 @@ func newTestRedis() *redis.Redis {
|
|
|
|
|
|
|
|
func newTestMiddleware(rds *redis.Redis, quota int) *RateLimitMiddleware {
|
|
func newTestMiddleware(rds *redis.Redis, quota int) *RateLimitMiddleware {
|
|
|
prefix := fmt.Sprintf("test_rl_%d_%d", time.Now().UnixNano(), rand.Intn(100000))
|
|
prefix := fmt.Sprintf("test_rl_%d_%d", time.Now().UnixNano(), rand.Intn(100000))
|
|
|
- return NewRateLimitMiddleware(rds, 60, quota, prefix)
|
|
|
|
|
|
|
+ return NewRateLimitMiddleware(rds, 60, quota, prefix, false)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// TC-0525: 正常请求(未超限)
|
|
// TC-0525: 正常请求(未超限)
|
|
@@ -86,7 +86,7 @@ func TestRateLimit_OverQuotaRejected(t *testing.T) {
|
|
|
assert.Equal(t, "请求过于频繁,请稍后再试", body.Msg)
|
|
assert.Equal(t, "请求过于频繁,请稍后再试", body.Msg)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// TC-0527: X-Forwarded-For被忽略(M-1安全修复验证)
|
|
|
|
|
|
|
+// TC-0527: behindProxy=false时XFF被忽略
|
|
|
func TestRateLimit_XForwardedForIgnored(t *testing.T) {
|
|
func TestRateLimit_XForwardedForIgnored(t *testing.T) {
|
|
|
rds := newTestRedis()
|
|
rds := newTestRedis()
|
|
|
m := newTestMiddleware(rds, 1)
|
|
m := newTestMiddleware(rds, 1)
|
|
@@ -112,7 +112,7 @@ func TestRateLimit_XForwardedForIgnored(t *testing.T) {
|
|
|
assert.Equal(t, 1, nextCount, "different X-Forwarded-For should NOT bypass rate limit; RemoteAddr is used")
|
|
assert.Equal(t, 1, nextCount, "different X-Forwarded-For should NOT bypass rate limit; RemoteAddr is used")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// TC-0528: X-Real-IP被忽略(M-1安全修复验证)
|
|
|
|
|
|
|
+// TC-0528: behindProxy=false时X-Real-IP被忽略
|
|
|
func TestRateLimit_XRealIPIgnored(t *testing.T) {
|
|
func TestRateLimit_XRealIPIgnored(t *testing.T) {
|
|
|
rds := newTestRedis()
|
|
rds := newTestRedis()
|
|
|
m := newTestMiddleware(rds, 1)
|
|
m := newTestMiddleware(rds, 1)
|
|
@@ -138,7 +138,7 @@ func TestRateLimit_XRealIPIgnored(t *testing.T) {
|
|
|
assert.Equal(t, 1, nextCount, "different X-Real-IP should NOT bypass rate limit; RemoteAddr is used")
|
|
assert.Equal(t, 1, nextCount, "different X-Real-IP should NOT bypass rate limit; RemoteAddr is used")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// TC-0529: IP从RemoteAddr获取
|
|
|
|
|
|
|
+// TC-0529: IP从RemoteAddr解析
|
|
|
func TestRateLimit_IPFromRemoteAddr(t *testing.T) {
|
|
func TestRateLimit_IPFromRemoteAddr(t *testing.T) {
|
|
|
rds := newTestRedis()
|
|
rds := newTestRedis()
|
|
|
m := newTestMiddleware(rds, 1)
|
|
m := newTestMiddleware(rds, 1)
|
|
@@ -198,3 +198,93 @@ func TestRateLimit_DifferentIPsIndependent(t *testing.T) {
|
|
|
handler(httptest.NewRecorder(), req4)
|
|
handler(httptest.NewRecorder(), req4)
|
|
|
assert.Equal(t, 2, nextCount, "addr2 should be over quota")
|
|
assert.Equal(t, 2, nextCount, "addr2 should be over quota")
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+func newTestMiddlewareProxy(rds *redis.Redis, quota int) *RateLimitMiddleware {
|
|
|
|
|
+ prefix := fmt.Sprintf("test_rl_%d_%d", time.Now().UnixNano(), rand.Intn(100000))
|
|
|
|
|
+ return NewRateLimitMiddleware(rds, 60, quota, prefix, true)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// TC-0531: behindProxy=true时信任X-Real-IP
|
|
|
|
|
+func TestRateLimit_BehindProxy_TrustsXRealIP(t *testing.T) {
|
|
|
|
|
+ rds := newTestRedis()
|
|
|
|
|
+ m := newTestMiddlewareProxy(rds, 1)
|
|
|
|
|
+ remoteAddr := uniqueIP() + ":12345"
|
|
|
|
|
+
|
|
|
|
|
+ var nextCount int
|
|
|
|
|
+ handler := m.Handle(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
+ nextCount++
|
|
|
|
|
+ w.WriteHeader(http.StatusOK)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ req1 := httptest.NewRequest(http.MethodPost, "/api/auth/login", nil)
|
|
|
|
|
+ req1.RemoteAddr = remoteAddr
|
|
|
|
|
+ req1.Header.Set("X-Real-IP", uniqueIP())
|
|
|
|
|
+ handler(httptest.NewRecorder(), req1)
|
|
|
|
|
+ assert.Equal(t, 1, nextCount)
|
|
|
|
|
+
|
|
|
|
|
+ req2 := httptest.NewRequest(http.MethodPost, "/api/auth/login", nil)
|
|
|
|
|
+ req2.RemoteAddr = remoteAddr
|
|
|
|
|
+ req2.Header.Set("X-Real-IP", uniqueIP())
|
|
|
|
|
+ handler(httptest.NewRecorder(), req2)
|
|
|
|
|
+ assert.Equal(t, 2, nextCount, "different X-Real-IP should have independent quotas when behindProxy=true")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// TC-0532: behindProxy=true时无X-Real-IP回退RemoteAddr
|
|
|
|
|
+func TestRateLimit_BehindProxy_FallbackToRemoteAddr(t *testing.T) {
|
|
|
|
|
+ rds := newTestRedis()
|
|
|
|
|
+ m := newTestMiddlewareProxy(rds, 1)
|
|
|
|
|
+ remoteAddr := uniqueIP() + ":12345"
|
|
|
|
|
+
|
|
|
|
|
+ var nextCount int
|
|
|
|
|
+ handler := m.Handle(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
+ nextCount++
|
|
|
|
|
+ w.WriteHeader(http.StatusOK)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ req1 := httptest.NewRequest(http.MethodPost, "/api/auth/login", nil)
|
|
|
|
|
+ req1.RemoteAddr = remoteAddr
|
|
|
|
|
+ handler(httptest.NewRecorder(), req1)
|
|
|
|
|
+ assert.Equal(t, 1, nextCount)
|
|
|
|
|
+
|
|
|
|
|
+ req2 := httptest.NewRequest(http.MethodPost, "/api/auth/login", nil)
|
|
|
|
|
+ req2.RemoteAddr = remoteAddr
|
|
|
|
|
+ handler(httptest.NewRecorder(), req2)
|
|
|
|
|
+ assert.Equal(t, 1, nextCount, "should fall back to RemoteAddr when X-Real-IP is absent")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// TC-0533: behindProxy=true时XFF仍被忽略
|
|
|
|
|
+func TestRateLimit_BehindProxy_XFFStillIgnored(t *testing.T) {
|
|
|
|
|
+ rds := newTestRedis()
|
|
|
|
|
+ m := newTestMiddlewareProxy(rds, 1)
|
|
|
|
|
+ remoteAddr := uniqueIP() + ":12345"
|
|
|
|
|
+
|
|
|
|
|
+ var nextCount int
|
|
|
|
|
+ handler := m.Handle(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
+ nextCount++
|
|
|
|
|
+ w.WriteHeader(http.StatusOK)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ req1 := httptest.NewRequest(http.MethodPost, "/api/auth/login", nil)
|
|
|
|
|
+ req1.RemoteAddr = remoteAddr
|
|
|
|
|
+ req1.Header.Set("X-Forwarded-For", uniqueIP())
|
|
|
|
|
+ handler(httptest.NewRecorder(), req1)
|
|
|
|
|
+ assert.Equal(t, 1, nextCount)
|
|
|
|
|
+
|
|
|
|
|
+ req2 := httptest.NewRequest(http.MethodPost, "/api/auth/login", nil)
|
|
|
|
|
+ req2.RemoteAddr = remoteAddr
|
|
|
|
|
+ req2.Header.Set("X-Forwarded-For", uniqueIP())
|
|
|
|
|
+ handler(httptest.NewRecorder(), req2)
|
|
|
|
|
+ assert.Equal(t, 1, nextCount, "X-Forwarded-For should NOT bypass rate limit even with behindProxy=true")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// TC-0534: RemoteAddr无端口格式
|
|
|
|
|
+func TestExtractClientIP_RemoteAddrNoPort(t *testing.T) {
|
|
|
|
|
+ req := httptest.NewRequest(http.MethodPost, "/api/test", nil)
|
|
|
|
|
+ req.RemoteAddr = "1.2.3.4"
|
|
|
|
|
+
|
|
|
|
|
+ ip := ExtractClientIP(req, false)
|
|
|
|
|
+ assert.Equal(t, "1.2.3.4", ip, "should return raw RemoteAddr when SplitHostPort fails")
|
|
|
|
|
+
|
|
|
|
|
+ ip2 := ExtractClientIP(req, true)
|
|
|
|
|
+ assert.Equal(t, "1.2.3.4", ip2, "behindProxy=true without X-Real-IP should also fallback")
|
|
|
|
|
+}
|