diff --git a/server-rs/crates/api-server/src/request_context.rs b/server-rs/crates/api-server/src/request_context.rs index 9cd65b410..9a1f58743 100644 --- a/server-rs/crates/api-server/src/request_context.rs +++ b/server-rs/crates/api-server/src/request_context.rs @@ -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"); } }