package middleware import ( "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" ) // newClientIPRouter 模拟生产环境的可信代理配置: // 仅信任回环地址(即 Caddy/nginx 主机)。 func newClientIPRouter() *gin.Engine { gin.SetMode(gin.TestMode) r := gin.New() if err := r.SetTrustedProxies([]string{"127.0.0.1", "::1"}); err != nil { panic(err) } r.GET("/ip", func(c *gin.Context) { c.String(http.StatusOK, c.ClientIP()) }) return r } func TestClientIPSpoofingBlocked(t *testing.T) { r := newClientIPRouter() // 直接(不受信任)客户端伪造 X-Forwarded-For 时, // 不得改变记录的 IP。 req := httptest.NewRequest(http.MethodGet, "/ip", nil) req.RemoteAddr = "203.0.113.5:12345" req.Header.Set("X-Forwarded-For", "6.6.6.6") w := httptest.NewRecorder() r.ServeHTTP(w, req) if got := w.Body.String(); got != "203.0.113.5" { t.Errorf("direct client with forged XFF: got %q, want 203.0.113.5", got) } // 可信代理(回环)转发真实链路:最右侧不受信任的条目生效, // 更早的(客户端提供的)条目被忽略。 req = httptest.NewRequest(http.MethodGet, "/ip", nil) req.RemoteAddr = "127.0.0.1:54321" req.Header.Set("X-Forwarded-For", "6.6.6.6, 198.51.100.42") w = httptest.NewRecorder() r.ServeHTTP(w, req) if got := w.Body.String(); got != "198.51.100.42" { t.Errorf("proxy-forwarded chain: got %q, want 198.51.100.42 (client-supplied entry must be ignored)", got) } // 可信代理转发单个条目:该条目即为客户端。 req = httptest.NewRequest(http.MethodGet, "/ip", nil) req.RemoteAddr = "127.0.0.1:54321" req.Header.Set("X-Forwarded-For", "198.51.100.42") w = httptest.NewRecorder() r.ServeHTTP(w, req) if got := w.Body.String(); got != "198.51.100.42" { t.Errorf("proxy-forwarded single entry: got %q, want 198.51.100.42", got) } }