后端:客户端 IP 只信 X-Real-IP,不再取 XFF 首段

- request_context.rs:client_ip_from_headers 改为优先 nginx 覆盖写入的 x-real-ip,x-forwarded-for 只作回退且取最后一段(nginx 用 $proxy_add_x_forwarded_for 追加的真实对端),不再信任可伪造的首段
- 影响两个调用方:公开游玩上报的匿名身份与 IP+game 限流键、微信支付下单的 payer_client_ip
- 更新测试:X-Real-IP 优先、XFF 回退取末段、空 X-Real-IP 回退与回环兜底
This commit is contained in:
2026-10-04 11:38:47 +08:00
parent c0148dda7e
commit 95705cbb11
@@ -107,19 +107,22 @@ pub async fn attach_request_context(mut request: Request, next: Next) -> Respons
.await
}
/// 从代理头解析客户端 IP:反代固定用 `x-forwarded-for` 的第一个地址,直连(本地开发)
/// 回退 `x-real-ip`,都拿不到时才兜底回环地址。
/// 从代理头解析客户端 IP。
///
/// 优先 `x-real-ip`:nginx 用 `$remote_addr` 覆盖写入,是真实 TCP 对端,调用方无法伪造。
/// `x-forwarded-for` 只作回退,并取**最后一段**——nginx 用 `$proxy_add_x_forwarded_for` 会把真实
/// 对端追加在末尾,前面几段是调用方自带的、可伪造。两者都拿不到时才兜底回环地址。
pub fn client_ip_from_headers(headers: &HeaderMap) -> String {
headers
.get("x-forwarded-for")
.get("x-real-ip")
.and_then(|value| value.to_str().ok())
.and_then(|value| value.split(',').next())
.map(str::trim)
.filter(|value| !value.is_empty())
.or_else(|| {
headers
.get("x-real-ip")
.get("x-forwarded-for")
.and_then(|value| value.to_str().ok())
.and_then(|value| value.rsplit(',').next())
.map(str::trim)
.filter(|value| !value.is_empty())
})
@@ -170,20 +173,33 @@ mod tests {
}
#[test]
fn client_ip_prefers_first_forwarded_address() {
fn client_ip_prefers_real_ip_over_forwarded_for() {
let mut headers = HeaderMap::new();
headers.insert(
"x-forwarded-for",
HeaderValue::from_static("203.0.113.7, 10.0.0.1"),
);
assert_eq!(client_ip_from_headers(&headers), "203.0.113.7");
headers.insert("x-real-ip", HeaderValue::from_static("198.51.100.9"));
assert_eq!(client_ip_from_headers(&headers), "198.51.100.9");
}
#[test]
fn client_ip_falls_back_to_real_ip_then_loopback() {
fn client_ip_forwarded_for_fallback_uses_last_address() {
// nginx 把真实对端追加在末尾,前面是调用方可伪造的值,只能取最后一段。
let mut headers = HeaderMap::new();
headers.insert("x-real-ip", HeaderValue::from_static("198.51.100.9"));
assert_eq!(client_ip_from_headers(&headers), "198.51.100.9");
headers.insert(
"x-forwarded-for",
HeaderValue::from_static("203.0.113.7, 10.0.0.1"),
);
assert_eq!(client_ip_from_headers(&headers), "10.0.0.1");
}
#[test]
fn client_ip_ignores_blank_real_ip_and_falls_back_then_loopback() {
let mut headers = HeaderMap::new();
headers.insert("x-real-ip", HeaderValue::from_static(" "));
headers.insert("x-forwarded-for", HeaderValue::from_static("10.0.0.1"));
assert_eq!(client_ip_from_headers(&headers), "10.0.0.1");
assert_eq!(client_ip_from_headers(&HeaderMap::new()), "127.0.0.1");
}
}