ratelimitMiddlewareXff_audit_test.go 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. package middleware
  2. import (
  3. "net/http/httptest"
  4. "testing"
  5. "github.com/stretchr/testify/assert"
  6. )
  7. // ---------------------------------------------------------------------------
  8. // 覆盖目标:审计第 6 轮 M-6 修复回归 —— ExtractClientIP 对 X-Forwarded-For 的支持。
  9. //
  10. // 原代码:behindProxy=true 时只认 X-Real-IP,不解析 X-Forwarded-For。
  11. // 生产事实:K8s Ingress-nginx 默认配置只写 X-Forwarded-For 不写 X-Real-IP,
  12. // 会导致线上 /auth/login /auth/refreshToken 的 IP 限流被降级为"共享一个桶或无限流"。
  13. //
  14. // 修复后的契约(每条一个 TC,保证任何一条被退回旧实现都 FAIL):
  15. // - XFF 首段 > XRI > RemoteAddr
  16. // - 每一段候选值都必须过 net.ParseIP 合法性校验
  17. // - 非法段自动跳过,进入下一个来源
  18. // - behindProxy=false 时完全忽略请求头,防止客户端伪造
  19. // ---------------------------------------------------------------------------
  20. // TC-0862: behindProxy=true + XFF 首段合法 → 返回首段。
  21. func TestExtractClientIP_XFFFirstValid(t *testing.T) {
  22. r := httptest.NewRequest("POST", "/x", nil)
  23. r.Header.Set("X-Forwarded-For", "1.1.1.1, 2.2.2.2, 3.3.3.3")
  24. r.Header.Set("X-Real-IP", "9.9.9.9") // 不应被用
  25. r.RemoteAddr = "5.5.5.5:8080" // 不应被用
  26. assert.Equal(t, "1.1.1.1", ExtractClientIP(r, true),
  27. "M-6:XFF 首段合法时优先返回,高于 XRI / RemoteAddr")
  28. }
  29. // TC-0863: behindProxy=true + XFF 全非法 + XRI 合法 → fallthrough 到 XRI。
  30. func TestExtractClientIP_XFFAllInvalid_FallbackXRI(t *testing.T) {
  31. r := httptest.NewRequest("POST", "/x", nil)
  32. r.Header.Set("X-Forwarded-For", "garbage, not-an-ip")
  33. r.Header.Set("X-Real-IP", "10.0.0.1")
  34. r.RemoteAddr = "5.5.5.5:8080"
  35. assert.Equal(t, "10.0.0.1", ExtractClientIP(r, true),
  36. "M-6:XFF 全不合法应当 fallthrough 到 X-Real-IP,不得返回 garbage 或 RemoteAddr")
  37. }
  38. // TC-0864: behindProxy=true + 两头均空 → 回落到 RemoteAddr 剥端口后的 host。
  39. func TestExtractClientIP_NoHeaders_FallbackRemoteAddr(t *testing.T) {
  40. r := httptest.NewRequest("POST", "/x", nil)
  41. r.RemoteAddr = "198.51.100.9:13579"
  42. assert.Equal(t, "198.51.100.9", ExtractClientIP(r, true),
  43. "M-6:所有代理头缺失时最终仍能回落到 RemoteAddr 剥端口")
  44. }
  45. // TC-0865: behindProxy=true + XFF 首段带两端空白 → trim 后仍解析合法,返回 trimmed 结果。
  46. func TestExtractClientIP_XFFWhitespaceTrimmed(t *testing.T) {
  47. r := httptest.NewRequest("POST", "/x", nil)
  48. r.Header.Set("X-Forwarded-For", " 3.3.3.3 , 4.4.4.4")
  49. assert.Equal(t, "3.3.3.3", ExtractClientIP(r, true),
  50. "M-6:XFF 首段 trim 后合法应当被采用;严禁保留首尾空白而误判")
  51. }
  52. // TC-0866: behindProxy=false —— 完全忽略 XFF / XRI,防止客户端伪造头。
  53. func TestExtractClientIP_BehindProxyFalse_IgnoreHeaders(t *testing.T) {
  54. r := httptest.NewRequest("POST", "/x", nil)
  55. r.Header.Set("X-Forwarded-For", "1.1.1.1") // 应被忽略
  56. r.Header.Set("X-Real-IP", "2.2.2.2") // 应被忽略
  57. r.RemoteAddr = "5.5.5.5:8080"
  58. assert.Equal(t, "5.5.5.5", ExtractClientIP(r, false),
  59. "M-6:behindProxy=false 时应完全忽略客户端注入的代理头")
  60. }
  61. // 补充:XFF 包含空段("1.1.1.1,,2.2.2.2")不应 panic,空段跳过后首段合法。
  62. func TestExtractClientIP_XFFEmptySegmentsSkipped(t *testing.T) {
  63. r := httptest.NewRequest("POST", "/x", nil)
  64. r.Header.Set("X-Forwarded-For", ",,,1.1.1.1,2.2.2.2")
  65. assert.Equal(t, "1.1.1.1", ExtractClientIP(r, true),
  66. "M-6:XFF 中空段必须跳过,不得 panic 或返回空串")
  67. }
  68. // 补充:XFF 全为合法 IPv6 地址也应能返回首段。
  69. func TestExtractClientIP_XFFIPv6FirstValid(t *testing.T) {
  70. r := httptest.NewRequest("POST", "/x", nil)
  71. r.Header.Set("X-Forwarded-For", "2001:db8::1, 2001:db8::2")
  72. assert.Equal(t, "2001:db8::1", ExtractClientIP(r, true),
  73. "M-6:IPv6 也是 net.ParseIP 合法值,XFF 首段应返回 IPv6")
  74. }