| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- package middleware
- import (
- "net/http/httptest"
- "testing"
- "github.com/stretchr/testify/assert"
- )
- // ---------------------------------------------------------------------------
- // 覆盖目标:审计第 6 轮 M-6 修复回归 —— ExtractClientIP 对 X-Forwarded-For 的支持。
- //
- // 原代码:behindProxy=true 时只认 X-Real-IP,不解析 X-Forwarded-For。
- // 生产事实:K8s Ingress-nginx 默认配置只写 X-Forwarded-For 不写 X-Real-IP,
- // 会导致线上 /auth/login /auth/refreshToken 的 IP 限流被降级为"共享一个桶或无限流"。
- //
- // 修复后的契约(每条一个 TC,保证任何一条被退回旧实现都 FAIL):
- // - XFF 首段 > XRI > RemoteAddr
- // - 每一段候选值都必须过 net.ParseIP 合法性校验
- // - 非法段自动跳过,进入下一个来源
- // - behindProxy=false 时完全忽略请求头,防止客户端伪造
- // ---------------------------------------------------------------------------
- // TC-0862: behindProxy=true + XFF 首段合法 → 返回首段。
- func TestExtractClientIP_XFFFirstValid(t *testing.T) {
- r := httptest.NewRequest("POST", "/x", nil)
- r.Header.Set("X-Forwarded-For", "1.1.1.1, 2.2.2.2, 3.3.3.3")
- r.Header.Set("X-Real-IP", "9.9.9.9") // 不应被用
- r.RemoteAddr = "5.5.5.5:8080" // 不应被用
- assert.Equal(t, "1.1.1.1", ExtractClientIP(r, true),
- "M-6:XFF 首段合法时优先返回,高于 XRI / RemoteAddr")
- }
- // TC-0863: behindProxy=true + XFF 全非法 + XRI 合法 → fallthrough 到 XRI。
- func TestExtractClientIP_XFFAllInvalid_FallbackXRI(t *testing.T) {
- r := httptest.NewRequest("POST", "/x", nil)
- r.Header.Set("X-Forwarded-For", "garbage, not-an-ip")
- r.Header.Set("X-Real-IP", "10.0.0.1")
- r.RemoteAddr = "5.5.5.5:8080"
- assert.Equal(t, "10.0.0.1", ExtractClientIP(r, true),
- "M-6:XFF 全不合法应当 fallthrough 到 X-Real-IP,不得返回 garbage 或 RemoteAddr")
- }
- // TC-0864: behindProxy=true + 两头均空 → 回落到 RemoteAddr 剥端口后的 host。
- func TestExtractClientIP_NoHeaders_FallbackRemoteAddr(t *testing.T) {
- r := httptest.NewRequest("POST", "/x", nil)
- r.RemoteAddr = "198.51.100.9:13579"
- assert.Equal(t, "198.51.100.9", ExtractClientIP(r, true),
- "M-6:所有代理头缺失时最终仍能回落到 RemoteAddr 剥端口")
- }
- // TC-0865: behindProxy=true + XFF 首段带两端空白 → trim 后仍解析合法,返回 trimmed 结果。
- func TestExtractClientIP_XFFWhitespaceTrimmed(t *testing.T) {
- r := httptest.NewRequest("POST", "/x", nil)
- r.Header.Set("X-Forwarded-For", " 3.3.3.3 , 4.4.4.4")
- assert.Equal(t, "3.3.3.3", ExtractClientIP(r, true),
- "M-6:XFF 首段 trim 后合法应当被采用;严禁保留首尾空白而误判")
- }
- // TC-0866: behindProxy=false —— 完全忽略 XFF / XRI,防止客户端伪造头。
- func TestExtractClientIP_BehindProxyFalse_IgnoreHeaders(t *testing.T) {
- r := httptest.NewRequest("POST", "/x", nil)
- r.Header.Set("X-Forwarded-For", "1.1.1.1") // 应被忽略
- r.Header.Set("X-Real-IP", "2.2.2.2") // 应被忽略
- r.RemoteAddr = "5.5.5.5:8080"
- assert.Equal(t, "5.5.5.5", ExtractClientIP(r, false),
- "M-6:behindProxy=false 时应完全忽略客户端注入的代理头")
- }
- // 补充:XFF 包含空段("1.1.1.1,,2.2.2.2")不应 panic,空段跳过后首段合法。
- func TestExtractClientIP_XFFEmptySegmentsSkipped(t *testing.T) {
- r := httptest.NewRequest("POST", "/x", nil)
- r.Header.Set("X-Forwarded-For", ",,,1.1.1.1,2.2.2.2")
- assert.Equal(t, "1.1.1.1", ExtractClientIP(r, true),
- "M-6:XFF 中空段必须跳过,不得 panic 或返回空串")
- }
- // 补充:XFF 全为合法 IPv6 地址也应能返回首段。
- func TestExtractClientIP_XFFIPv6FirstValid(t *testing.T) {
- r := httptest.NewRequest("POST", "/x", nil)
- r.Header.Set("X-Forwarded-For", "2001:db8::1, 2001:db8::2")
- assert.Equal(t, "2001:db8::1", ExtractClientIP(r, true),
- "M-6:IPv6 也是 net.ParseIP 合法值,XFF 首段应返回 IPv6")
- }
|