Skip to content

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: true

TIP

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 头部标准

通过合理配置这些过滤器,可以确保下游服务获得准确的客户端信息,同时保证系统的安全性和性能。 🎉