Skip to content

SaveSession 网关过滤器工厂

概述

SaveSession 网关过滤器工厂是 Spring Cloud Gateway 中的一个重要组件,它的主要作用是在将请求转发到下游服务之前强制执行 WebSession::save 操作。这个过滤器在使用 Spring Session 与延迟数据存储(如 Redis、数据库等)时特别有用,确保会话状态在转发请求之前已经被保存。

SaveSession 过滤器确保会话数据在请求转发前被持久化,这对于分布式系统中的会话管理至关重要。

核心问题解决

会话延迟保存的问题

在分布式微服务架构中,默认情况下 Spring Session 采用延迟保存策略来优化性能。这意味着会话数据可能在请求处理完成后才会被保存到存储介质中。但在网关场景下,这可能导致以下问题:

  1. 会话数据丢失:如果网关在转发请求前崩溃,未保存的会话数据将丢失
  2. 下游服务无法获取最新会话:下游服务可能读取到过期的会话信息
  3. 安全认证问题:在集成 Spring Security 时,安全详细信息可能无法正确传递

实际业务场景

场景一:电商购物车管理

在电商系统中,用户的购物车信息通常存储在会话中。当用户通过网关访问不同的微服务(商品服务、订单服务等)时,需要确保购物车数据在每次服务调用前都已正确保存。

场景二:用户认证状态传递

在需要身份认证的系统中,用户的登录状态和权限信息存储在会话中。SaveSession 过滤器确保这些关键的安全信息在请求转发前已被保存,避免下游服务获取到过期的认证信息。

配置方式

YAML 配置

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: save_session_route
          uri: https://api.example.com
          predicates:
            - Path=/api/**
          filters:
            - SaveSession # 启用 SaveSession 过滤器
yaml
spring:
  cloud:
    gateway:
      routes:
        # 购物车服务路由
        - id: cart_service
          uri: lb://cart-service
          predicates:
            - Path=/cart/**
          filters:
            - SaveSession
            - StripPrefix=1

        # 订单服务路由
        - id: order_service
          uri: lb://order-service
          predicates:
            - Path=/order/**
          filters:
            - SaveSession
            - StripPrefix=1

  # Spring Session 配置
  session:
    store-type: redis
    redis:
      namespace: spring:session
    timeout: 30m

Java 配置方式

kotlin
@Configuration
@EnableWebFluxSecurity
class GatewayConfig {

    /**
     * 通过编程方式配置路由和过滤器
     */
    @Bean
    fun routeLocator(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("save_session_route") { route ->
                route.path("/api/**")
                    .filters { filter ->
                        filter.saveSession()  // 添加 SaveSession 过滤器
                            .stripPrefix(1)
                    }
                    .uri("lb://backend-service")
            }
            .route("secure_route") { route ->
                route.path("/secure/**")
                    .filters { filter ->
                        filter.saveSession()  // 确保安全信息被保存
                            .stripPrefix(1)
                    }
                    .uri("lb://secure-service")
            }
            .build()
    }
}
java
@Configuration
@EnableWebFluxSecurity
public class GatewayConfig {

    /**
     * 通过编程方式配置路由和过滤器
     */
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("save_session_route", route ->
                route.path("/api/**")
                    .filters(filter ->
                        filter.saveSession()  // 添加 SaveSession 过滤器
                            .stripPrefix(1)
                    )
                    .uri("lb://backend-service")
            )
            .route("secure_route", route ->
                route.path("/secure/**")
                    .filters(filter ->
                        filter.saveSession()  // 确保安全信息被保存
                            .stripPrefix(1)
                    )
                    .uri("lb://secure-service")
            )
            .build();
    }
}

自定义 SaveSession 过滤器

有时我们需要更细粒度的控制,可以创建自定义的 SaveSession 过滤器:

kotlin
@Component
class CustomSaveSessionFilter : AbstractGatewayFilterFactory<Any>() {

    override fun apply(config: Any?): GatewayFilter {
        return GatewayFilter { exchange, chain ->
            // 获取当前会话
            exchange.session.flatMap { session ->
                // 记录会话保存日志
                logger.info("强制保存会话: ${session.id}")

                // 保存会话数据
                session.save().then(
                    // 继续处理请求链
                    chain.filter(exchange)
                )
            }.switchIfEmpty(
                // 如果没有会话,直接继续处理
                chain.filter(exchange)
            )
        }
    }

    companion object {
        private val logger = LoggerFactory.getLogger(CustomSaveSessionFilter::class.java)
    }
}

与 Spring Security 集成

当系统集成了 Spring Security 时,SaveSession 过滤器变得更加重要,因为它确保安全上下文信息能够正确传递给下游服务。

kotlin
@Configuration
@EnableWebFluxSecurity
@EnableReactiveRedisWebSession
class SecurityConfig {

    /**
     * 配置安全过滤器链
     */
    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http
            .csrf().disable()
            .authorizeExchange { exchanges ->
                exchanges
                    .pathMatchers("/public/**").permitAll()
                    .pathMatchers("/admin/**").hasRole("ADMIN")
                    .anyExchange().authenticated()
            }
            .oauth2Login { oauth2 ->
                oauth2.authenticationSuccessHandler { exchange, authentication ->
                    // 认证成功后,确保会话被保存
                    exchange.exchange.session.flatMap { session ->
                        // 保存认证信息到会话
                        session.attributes["authentication"] = authentication
                        session.save()
                    }.then()
                }
            }
            .build()
    }

    /**
     * 配置会话存储
     */
    @Bean
    fun redisSessionRepository(
        connectionFactory: ReactiveRedisConnectionFactory
    ): ReactiveRedisSessionRepository {
        return ReactiveRedisSessionRepository(connectionFactory).apply {
            setDefaultMaxInactiveInterval(Duration.ofMinutes(30))
            setKeyNamespace("gateway:session")
        }
    }
}

最佳实践

1. 选择性使用

不是所有路由都需要 SaveSession 过滤器。只在需要确保会话状态一致性的路由上使用,避免不必要的性能开销。

kotlin
// ✅ 推荐:只在需要的路由上使用
.route("user_session_route") { route ->
    route.path("/user/**", "/account/**")
        .filters { it.saveSession() }  // 用户相关路由需要会话
        .uri("lb://user-service")
}

// ❌ 避免:在静态资源路由上使用
.route("static_resources") { route ->
    route.path("/static/**")
        // .filters { it.saveSession() }  // 静态资源不需要会话
        .uri("lb://cdn-service")
}

2. 性能监控

添加监控来跟踪 SaveSession 过滤器的性能影响:

kotlin
@Component
class SaveSessionMetricsFilter : GlobalFilter, Ordered {

    private val meterRegistry: MeterRegistry = Metrics.globalRegistry

    override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
        val timer = Timer.start(meterRegistry)

        return chain.filter(exchange)
            .doFinally {
                timer.stop(Timer.builder("gateway.save_session.duration")
                    .tag("route", exchange.request.path.toString())
                    .register(meterRegistry))
            }
    }

    override fun getOrder(): Int = -1  // 高优先级
}

3. 错误处理

kotlin
@Component
class RobustSaveSessionFilter : AbstractGatewayFilterFactory<Any>() {

    override fun apply(config: Any?): GatewayFilter {
        return GatewayFilter { exchange, chain ->
            exchange.session.flatMap { session ->
                session.save()
                    .timeout(Duration.ofSeconds(5))  // 设置超时
                    .onErrorResume { error ->
                        // 记录错误但不中断请求流
                        logger.warn("会话保存失败: ${error.message}")
                        Mono.empty()
                    }
                    .then(chain.filter(exchange))
            }.switchIfEmpty(chain.filter(exchange))
        }
    }
}

常见问题和解决方案

问题 1:会话保存超时

在高并发场景下,会话保存可能因为存储系统压力而超时。

解决方案

kotlin
spring:
  session:
    redis:
      # 增加连接池配置
      lettuce:
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
      # 设置合理的超时时间
      timeout: 3s

问题 2:循环依赖

在某些配置下可能出现 SaveSession 过滤器与 Security 配置的循环依赖。

解决方案

kotlin
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

    @Bean
    @DependsOn("routeLocator")  // 明确依赖关系
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        // 安全配置
    }
}

总结

SaveSession 网关过滤器工厂是 Spring Cloud Gateway 中确保会话一致性的重要工具。它通过在请求转发前强制保存会话状态,解决了分布式系统中会话管理的关键问题。正确使用这个过滤器可以:

  • ✅ 确保会话数据的一致性和可靠性
  • ✅ 提高系统的容错能力
  • ✅ 保证安全认证信息的正确传递
  • ✅ 提升用户体验

在使用 SaveSession 过滤器时,要平衡会话一致性需求和系统性能,选择性地在需要的路由上应用,并配合适当的监控和错误处理机制。