Appearance
SaveSession 网关过滤器工厂
概述
SaveSession 网关过滤器工厂是 Spring Cloud Gateway 中的一个重要组件,它的主要作用是在将请求转发到下游服务之前强制执行 WebSession::save 操作。这个过滤器在使用 Spring Session 与延迟数据存储(如 Redis、数据库等)时特别有用,确保会话状态在转发请求之前已经被保存。
SaveSession 过滤器确保会话数据在请求转发前被持久化,这对于分布式系统中的会话管理至关重要。
核心问题解决
会话延迟保存的问题
在分布式微服务架构中,默认情况下 Spring Session 采用延迟保存策略来优化性能。这意味着会话数据可能在请求处理完成后才会被保存到存储介质中。但在网关场景下,这可能导致以下问题:
- 会话数据丢失:如果网关在转发请求前崩溃,未保存的会话数据将丢失
- 下游服务无法获取最新会话:下游服务可能读取到过期的会话信息
- 安全认证问题:在集成 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: 30mJava 配置方式
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 过滤器时,要平衡会话一致性需求和系统性能,选择性地在需要的路由上应用,并配合适当的监控和错误处理机制。