Appearance
HTTP 请求头过滤器
Spring Cloud Gateway 的 HttpHeadersFilters 是在将请求发送到下游服务之前应用于请求的过滤器,例如在 NettyRoutingFilter 中使用。这些过滤器主要用于处理和管理 HTTP 头部信息,确保请求在代理过程中能够正确传递相关的网络信息。
业务背景
在微服务架构中,客户端请求通常会经过多个代理层(如负载均衡器、API 网关等)才能到达最终的服务。在这个过程中,原始请求的信息(如客户端真实 IP、协议、端口等)可能会丢失。HTTP 头部过滤器就是为了解决这个问题,确保下游服务能够获取到准确的客户端信息。
Forwarded Headers Filter
功能说明
Forwarded Headers Filter 创建一个 Forwarded 头部发送给下游服务。它将当前请求的 Host 头部、协议和端口添加到任何现有的 Forwarded 头部中。
配置激活
要激活此过滤器,需要设置 spring.cloud.gateway.server.webflux.trusted-proxies 属性为一个 Java 正则表达式。这个正则表达式定义了当它们出现在 Forwarded 头部中时可信任的代理。
kotlin
// application.yml 配置示例
@Configuration
class GatewayConfig {
@Bean
fun routeLocator(builder: RouteLocatorBuilder): RouteLocator {
return builder.routes()
.route("forwarded-example") { r ->
r.path("/api/**")
.uri("http://downstream-service:8080")
}
.build()
}
}配置属性
yaml
spring:
cloud:
gateway:
server:
webflux:
# 定义可信任的代理 IP 地址模式
trusted-proxies: "192\\.168\\..*|10\\..*"
forwarded:
# 启用 Forwarded by 头部部分(默认为 false)
by:
enabled: trueTIP
Forwarded by 头部部分可以通过设置 spring.cloud.gateway.server.webflux.forwarded.by.enabled=true 来启用(默认为 false)
实际业务场景
在电商系统中,当用户通过 CDN 和负载均衡器访问购物车服务时,购物车服务需要知道用户的真实 IP 地址用于风控和地域判断:
kotlin
@RestController
class ShoppingCartController {
@GetMapping("/cart")
fun getCart(request: ServerHttpRequest): ResponseEntity<Cart> {
// 从 Forwarded 头部获取客户端真实信息
val forwardedHeader = request.headers.getFirst("Forwarded")
val clientInfo = parseForwardedHeader(forwardedHeader)
// 基于客户端 IP 进行风控检查
if (riskControlService.isHighRisk(clientInfo.clientIp)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build()
}
return ResponseEntity.ok(cartService.getCart(clientInfo))
}
private fun parseForwardedHeader(header: String?): ClientInfo {
// 解析 Forwarded 头部: for="192.168.1.100:8080";host="example.com";proto="https"
// 实际实现会更复杂,这里简化展示
return ClientInfo(
clientIp = extractClientIp(header),
host = extractHost(header),
protocol = extractProtocol(header)
)
}
}
data class ClientInfo(
val clientIp: String,
val host: String,
val protocol: String
)RemoveHopByHop Headers Filter
功能说明
RemoveHopByHop Headers Filter 从转发的请求中移除逐跳头部。默认移除的头部列表来自 IETF 标准。
默认移除的头部
以下是默认被移除的头部:
- Connection: 连接管理信息
- Keep-Alive: 长连接保持信息
- Proxy-Authenticate: 代理认证信息
- Proxy-Authorization: 代理授权信息
- TE: 传输编码偏好
- Trailer: 尾部头部信息
- Transfer-Encoding: 传输编码方式
- Upgrade: 协议升级信息
自定义配置
yaml
spring:
cloud:
gateway:
server:
webflux:
filter:
remove-hop-by-hop:
# 自定义要移除的头部列表
headers:
- Connection
- Keep-Alive
- Custom-Internal-Header实际业务场景
在内部服务通信中,某些头部信息只在特定的网络层级有意义,不应该传递给最终的业务服务:
kotlin
@Component
class CustomHopByHopFilter : GlobalFilter, Ordered {
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
val request = exchange.request
// 记录被移除的内部头部(用于调试)
val internalHeaders = request.headers
.filter { (key, _) -> key.startsWith("X-Internal-") }
if (internalHeaders.isNotEmpty()) {
logger.debug("移除内部头部: ${internalHeaders.keys}")
}
return chain.filter(exchange)
}
override fun getOrder(): Int = NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1
companion object {
private val logger = LoggerFactory.getLogger(CustomHopByHopFilter::class.java)
}
}移除过多的头部可能会导致下游服务无法正常工作,请谨慎配置自定义的移除列表。
XForwarded Headers Filter
功能说明
XForwarded Headers Filter 创建各种 X-Forwarded-* 头部发送给下游服务。它使用当前请求的 Host 头部、协议、端口和路径来创建各种头部。
激活配置
要激活此过滤器,需要设置 spring.cloud.gateway.server.webflux.trusted-proxies 属性为一个 Java 正则表达式。
头部控制配置
可以通过以下布尔属性控制各个头部的创建(默认为 true):
yaml
spring:
cloud:
gateway:
server:
webflux:
trusted-proxies: "192\\.168\\..*|10\\..*"
x-forwarded:
# 控制各个 X-Forwarded 头部的启用状态
for-enabled: true # X-Forwarded-For
host-enabled: true # X-Forwarded-Host
port-enabled: true # X-Forwarded-Port
proto-enabled: true # X-Forwarded-Proto
prefix-enabled: true # X-Forwarded-Prefix
# 控制是否追加多个头部值(默认为 true)
for-append: true
host-append: true
port-append: true
proto-append: true
prefix-append: true实际业务场景
在用户认证服务中,需要根据客户端的真实 IP 和协议信息进行安全策略判断:
kotlin
@RestController
class UserAuthController {
@PostMapping("/login")
fun login(
@RequestBody loginRequest: LoginRequest,
request: ServerHttpRequest
): ResponseEntity<LoginResponse> {
// 获取客户端真实信息
val clientInfo = extractClientInfo(request)
// 基于客户端信息进行安全检查
val securityContext = SecurityContext(
clientIp = clientInfo.realIp,
isHttps = clientInfo.isSecure,
host = clientInfo.host,
userAgent = request.headers.getFirst("User-Agent")
)
// 执行登录逻辑
val loginResult = authService.authenticate(loginRequest, securityContext)
return ResponseEntity.ok(loginResult)
}
private fun extractClientInfo(request: ServerHttpRequest): ClientInfo {
val headers = request.headers
return ClientInfo(
realIp = headers.getFirst("X-Forwarded-For")
?: request.remoteAddress?.address?.hostAddress
?: "unknown",
isSecure = headers.getFirst("X-Forwarded-Proto") == "https",
host = headers.getFirst("X-Forwarded-Host")
?: headers.getFirst("Host")
?: "unknown",
port = headers.getFirst("X-Forwarded-Port")?.toIntOrNull()
?: request.uri.port
)
}
}
data class ClientInfo(
val realIp: String,
val isSecure: Boolean,
val host: String,
val port: Int?
)
data class SecurityContext(
val clientIp: String,
val isHttps: Boolean,
val host: String,
val userAgent: String?
)java
@RestController
public class UserAuthController {
@PostMapping("/login")
public ResponseEntity<LoginResponse> login(
@RequestBody LoginRequest loginRequest,
ServerHttpRequest request) {
// 获取客户端真实信息
ClientInfo clientInfo = extractClientInfo(request);
// 基于客户端信息进行安全检查
SecurityContext securityContext = new SecurityContext(
clientInfo.getRealIp(),
clientInfo.isSecure(),
clientInfo.getHost(),
request.getHeaders().getFirst("User-Agent")
);
// 执行登录逻辑
LoginResponse loginResult = authService.authenticate(loginRequest, securityContext);
return ResponseEntity.ok(loginResult);
}
private ClientInfo extractClientInfo(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String realIp = Optional.ofNullable(headers.getFirst("X-Forwarded-For"))
.orElse(Optional.ofNullable(request.getRemoteAddress())
.map(addr -> addr.getAddress().getHostAddress())
.orElse("unknown"));
boolean isSecure = "https".equals(headers.getFirst("X-Forwarded-Proto"));
String host = Optional.ofNullable(headers.getFirst("X-Forwarded-Host"))
.orElse(Optional.ofNullable(headers.getFirst("Host"))
.orElse("unknown"));
Integer port = Optional.ofNullable(headers.getFirst("X-Forwarded-Port"))
.map(Integer::parseInt)
.orElse(request.getURI().getPort());
return new ClientInfo(realIp, isSecure, host, port);
}
}头部信息流转图
最佳实践
1. 安全配置
kotlin
@Configuration
class SecurityGatewayConfig {
@Bean
fun webFluxSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http
.authorizeExchange { exchanges ->
exchanges
.pathMatchers("/actuator/**").hasRole("ADMIN")
.anyExchange().authenticated()
}
.oauth2ResourceServer { oauth2 ->
oauth2.jwt { jwt ->
jwt.jwtDecoder(jwtDecoder())
}
}
.build()
}
@Bean
fun customHeaderFilter(): GlobalFilter {
return GlobalFilter { exchange, chain ->
val request = exchange.request
val clientIp = request.headers.getFirst("X-Forwarded-For")
// 验证可信代理
if (clientIp != null && !isTrustedProxy(clientIp)) {
// 记录可疑请求
logger.warn("来自不可信代理的请求: $clientIp")
}
chain.filter(exchange)
}
}
private fun isTrustedProxy(ip: String): Boolean {
// 实现代理IP验证逻辑
return ip.matches(Regex("^(192\\.168\\.|10\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.).*"))
}
}2. 监控和日志
kotlin
@Component
class HeaderFilterMonitor {
private val meterRegistry: MeterRegistry = Metrics.globalRegistry
private val headerProcessedCounter = Counter.builder("gateway.headers.processed")
.description("处理的头部数量")
.register(meterRegistry)
@EventListener
fun onRequestProcessed(event: RequestProcessedEvent) {
headerProcessedCounter.increment(
Tags.of(
"filter_type", event.filterType,
"headers_count", event.headersCount.toString()
)
)
}
}在生产环境中,务必正确配置 `trusted-proxies` 属性,避免 IP 欺骗攻击。只信任已知的代理服务器 IP 地址。
HTTP 头部过滤器的执行顺序很重要。确保在自定义过滤器中正确处理这些标准头部,避免信息丢失或冲突。
总结
Spring Cloud Gateway 的 HTTP 头部过滤器为微服务架构中的请求代理提供了强大的头部管理能力:
- Forwarded Headers Filter: 标准化的代理信息传递
- RemoveHopByHop Headers Filter: 清理不必要的网络层头部
- XForwarded Headers Filter: 兼容传统的 X-Forwarded 头部标准
通过合理配置这些过滤器,可以确保下游服务获得准确的客户端信息,同时保证系统的安全性和性能。 🎉