Skip to content

AddRequestParameter GatewayFilter Factory

概述

AddRequestParameter GatewayFilter Factory 是 Spring Cloud Gateway 中的一个内置过滤器工厂,用于向转发到下游服务的请求中动态添加请求参数。这个过滤器在 API 网关场景中非常有用,可以在不修改客户端代码的情况下,为后端服务提供额外的参数信息。

核心概念

工作原理

AddRequestParameter 过滤器接受两个参数:

  • name: 要添加的参数名称
  • value: 要添加的参数值

该过滤器会将指定的参数以查询字符串的形式添加到转发给下游服务的请求中。

实际业务场景

场景 1: 用户身份标识传递

在微服务架构中,网关通常负责用户认证,需要将用户信息传递给下游服务:

这种方式特别适用于需要向所有下游服务传递统一用户标识的场景,避免在每个服务中重复进行用户信息解析。

场景 2: 租户信息注入

在多租户 SaaS 应用中,网关可以根据请求域名或其他特征自动注入租户信息:

通过网关统一处理租户信息,可以确保数据隔离的一致性,避免租户信息泄露的安全风险。

场景 3: API 版本控制

为了向后兼容,可以在网关层自动添加 API 版本参数:

这种方式可以让老版本的客户端无需修改即可访问新版本的 API,提高系统的向后兼容性。

配置方式

基础配置

最简单的配置方式是直接指定参数名和值:

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: add_request_parameter_route
          uri: https://example.org
          filters:
            - AddRequestParameter=red, blue

这个配置会将 red=blue 添加到所有匹配请求的查询字符串中。

使用 URI 变量

AddRequestParameter 支持使用 URI 变量,可以动态地根据请求路径或主机信息生成参数值:

yaml
spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        predicates:
        - Host: {segment}.myhost.org
        filters:
        - AddRequestParameter=foo, bar-{segment}

在这个例子中,如果请求的主机是 tenant1.myhost.org,则会添加 foo=bar-tenant1 参数。

Kotlin 代码实现

1. 配置类方式

kotlin
@Configuration
@EnableConfigurationProperties
class GatewayConfig {

    /**
     * 用户身份标识路由配置
     * 为所有用户API请求添加用户ID参数
     */
    @Bean
    fun userApiRoute(): RouteLocator {
        return RouteLocatorBuilder(RouteLocatorBuilder::class.java)
            .routes()
            .route("user-api") { r ->
                r.path("/api/user/**")
                    .filters { f ->
                        // 添加用户ID参数,通常从JWT token中提取
                        f.addRequestParameter("userId", "12345")
                        // 添加请求来源标识
                        f.addRequestParameter("source", "gateway")
                    }
                    .uri("http://user-service")
            }
            .build()
    }

    /**
     * 多租户路由配置
     * 根据子域名动态添加租户信息
     */
    @Bean
    fun tenantApiRoute(): RouteLocator {
        return RouteLocatorBuilder(RouteLocatorBuilder::class.java)
            .routes()
            .route("tenant-api") { r ->
                r.host("{tenant}.api.mycompany.com")
                    .and()
                    .path("/api/**")
                    .filters { f ->
                        // 使用URI变量动态设置租户ID
                        f.addRequestParameter("tenantId", "{tenant}")
                        // 添加API版本信息
                        f.addRequestParameter("apiVersion", "v1")
                    }
                    .uri("http://backend-service")
            }
            .build()
    }
}
java
@Configuration
@EnableConfigurationProperties
public class GatewayConfig {

    /**
     * 用户身份标识路由配置
     * 为所有用户API请求添加用户ID参数
     */
    @Bean
    public RouteLocator userApiRoute(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("user-api", r -> r
                .path("/api/user/**")
                .filters(f -> f
                    // 添加用户ID参数,通常从JWT token中提取
                    .addRequestParameter("userId", "12345")
                    // 添加请求来源标识
                    .addRequestParameter("source", "gateway")
                )
                .uri("http://user-service")
            )
            .build();
    }

    /**
     * 多租户路由配置
     * 根据子域名动态添加租户信息
     */
    @Bean
    public RouteLocator tenantApiRoute(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("tenant-api", r -> r
                .host("{tenant}.api.mycompany.com")
                .and()
                .path("/api/**")
                .filters(f -> f
                    // 使用URI变量动态设置租户ID
                    .addRequestParameter("tenantId", "{tenant}")
                    // 添加API版本信息
                    .addRequestParameter("apiVersion", "v1")
                )
                .uri("http://backend-service")
            )
            .build();
    }
}

2. 自定义过滤器工厂

对于更复杂的业务逻辑,可以创建自定义的参数添加过滤器:

kotlin
/**
 * 自定义用户信息参数过滤器工厂
 * 从JWT token中提取用户信息并添加为请求参数
 */
@Component
class UserInfoGatewayFilterFactory : AbstractGatewayFilterFactory<UserInfoGatewayFilterFactory.Config>() {

    /**
     * 配置类,定义过滤器的配置参数
     */
    data class Config(
        var includeUserId: Boolean = true,     // 是否包含用户ID
        var includeUserRole: Boolean = false,  // 是否包含用户角色
        var includeCompanyId: Boolean = false  // 是否包含公司ID
    )

    override fun apply(config: Config): GatewayFilter {
        return GatewayFilter { exchange, chain ->
            // 从请求头中获取JWT token
            val token = exchange.request.headers.getFirst("Authorization")

            if (token != null && token.startsWith("Bearer ")) {
                val jwtToken = token.substring(7)

                // 解析JWT token获取用户信息(这里简化处理)
                val userInfo = parseJwtToken(jwtToken)

                // 构建新的请求,添加用户信息参数
                val modifiedRequest = exchange.request.mutate()

                if (config.includeUserId && userInfo.userId != null) {
                    modifiedRequest.queryParam("userId", userInfo.userId)
                }

                if (config.includeUserRole && userInfo.role != null) {
                    modifiedRequest.queryParam("userRole", userInfo.role)
                }

                if (config.includeCompanyId && userInfo.companyId != null) {
                    modifiedRequest.queryParam("companyId", userInfo.companyId)
                }

                // 创建新的请求交换对象
                val modifiedExchange = exchange.mutate()
                    .request(modifiedRequest.build())
                    .build()

                chain.filter(modifiedExchange)
            } else {
                // 如果没有token,直接传递请求
                chain.filter(exchange)
            }
        }
    }

    /**
     * 解析JWT token获取用户信息
     * 实际项目中应该使用专门的JWT解析库
     */
    private fun parseJwtToken(token: String): UserInfo {
        // 这里应该实现真正的JWT解析逻辑
        return UserInfo(
            userId = "12345",
            role = "admin",
            companyId = "company-001"
        )
    }

    /**
     * 用户信息数据类
     */
    data class UserInfo(
        val userId: String?,
        val role: String?,
        val companyId: String?
    )

    override fun getConfigClass(): Class<Config> = Config::class.java
}

3. 在配置中使用自定义过滤器

kotlin
@Configuration
class CustomGatewayConfig {

    @Bean
    fun customUserInfoRoute(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("user-info-api") { r ->
                r.path("/api/secure/**")
                    .filters { f ->
                        // 使用自定义过滤器添加用户信息
                        f.filter(UserInfoGatewayFilterFactory().apply(
                            UserInfoGatewayFilterFactory.Config(
                                includeUserId = true,
                                includeUserRole = true,
                                includeCompanyId = true
                            )
                        ))
                    }
                    .uri("http://secure-service")
            }
            .build()
    }
}

配置示例详解

1. 简单参数添加

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: simple_param_route
          uri: http://backend-service
          predicates:
            - Path=/api/**
          filters:
            - AddRequestParameter=source, gateway # 添加固定参数
            - AddRequestParameter=timestamp, ${timestamp} # 支持SpEL表达式

2. 动态参数添加

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: dynamic_param_route
          uri: http://tenant-service
          predicates:
            - Host={tenant}.api.example.com
            - Path=/api/v1/**
          filters:
            - AddRequestParameter=tenant, {tenant} # 使用路径变量
            - AddRequestParameter=version, v1 # 添加版本信息
            - AddRequestParameter=region, us-east-1 # 添加区域信息

3. 条件性参数添加

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: conditional_param_route
          uri: http://api-service
          predicates:
            - Path=/api/**
            - Header=X-User-Type, premium # 只对高级用户生效
          filters:
            - AddRequestParameter=userType, premium
            - AddRequestParameter=features, advanced

最佳实践

1. 参数命名规范

建议使用清晰的参数命名规范,如:

  • 系统级参数:_gateway_, _system_ 前缀
  • 用户级参数:user_, account_ 前缀
  • 业务级参数:tenant_, org_ 前缀

2. 安全考虑

注意不要在参数中暴露敏感信息:

  • 避免直接传递密码或密钥
  • 敏感信息应该进行加密或使用引用方式
  • 定期审查添加的参数内容

3. 性能优化

合理控制添加的参数数量和大小:

  • 避免添加过多参数影响性能
  • 大数据量参数考虑使用请求体传递
  • 监控参数添加对请求延迟的影响

4. 调试和监控

kotlin
/**
 * 参数添加监控配置
 */
@Configuration
class ParameterMonitoringConfig {

    @Bean
    fun parameterLoggingFilter(): GlobalFilter {
        return GlobalFilter { exchange, chain ->
            // 记录添加的参数信息
            val queryParams = exchange.request.queryParams
            log.debug("Request parameters: {}", queryParams)

            chain.filter(exchange).doOnSuccess {
                log.debug("Successfully processed request with parameters")
            }
        }
    }

    companion object {
        private val log = LoggerFactory.getLogger(ParameterMonitoringConfig::class.java)
    }
}

常见问题和解决方案

1. 参数重复问题

如果请求中已存在同名参数,`AddRequestParameter` 会添加新的参数值,不会覆盖原有值。

解决方案:

kotlin
// 如果需要覆盖现有参数,可以先移除再添加
.filter { f ->
    f.removeRequestParameter("existingParam")
     .addRequestParameter("existingParam", "newValue")
}

2. URI 变量不生效

确保 URI 变量的名称与 Predicate 中定义的变量名完全匹配,包括大小写。

3. 特殊字符处理

kotlin
/**
 * 处理特殊字符的参数值
 */
fun encodeParameterValue(value: String): String {
    return URLEncoder.encode(value, StandardCharsets.UTF_8)
}

总结

AddRequestParameter GatewayFilter Factory 是 Spring Cloud Gateway 中一个强大而灵活的功能,它允许我们在 API 网关层动态地为请求添加参数。通过合理使用这个功能,我们可以:

  • ✅ 实现统一的用户身份传递
  • ✅ 简化多租户架构的实现
  • ✅ 提供灵活的 API 版本控制
  • ✅ 减少下游服务的复杂性

在实际使用中,建议结合具体的业务场景,制定清晰的参数添加策略,并注意安全性和性能方面的考虑。