Spring Cloud Gateway 本身不提供开箱即用的会话鉴权开关,通常需结合 Spring Session 与 Redis,并通过自定义 GlobalFilter 实现,适合需要维持用户登录状态的中心化网关场景。
先说结论:该方案适合有状态认证需求的网关层,需先准备 Redis 环境与响应式依赖,验收时重点检查 Cookie 传递与 Redis 键值。
- 适合:需要在网关层统一拦截并校验登录状态的业务场景
- 先准备:确保 Redis 服务可用,引入 spring-session-data-redis 与响应式栈依赖
- 验收:通过浏览器请求携带 Cookie,确认 Redis 中存在会话键且网关放行
核心依赖引入
在 pom.xml 中引入 Redis 响应式客户端与 Spring Session 依赖,注意不要混入 Servlet 栈依赖(如 spring-boot-starter-web),否则会导致启动冲突。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>完整配置示例 (application.yml)
配置 Redis 连接信息、会话存储类型以及 Cookie 的跨域属性。确保 `spring.session.store-type` 设置为 `redis`。
server:
port: 8080
cookie:
same-site: none
secure: true
spring:
redis:
host: localhost
port: 6379
password: your_password
session:
store-type: redis
redis:
namespace: spring:session
timeout: 30mRedis 会话序列化配置
为避免网关与认证服务端的会话数据无法读取,需统一序列化方式。推荐使用 JSON 序列化而非 JDK 默认序列化,以便跨语言或跨版本兼容。
@Configuration
public class RedisConfig {
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
@Bean
public ReactiveRedisOperations<String, Object> reactiveRedisOperations(RedisConnectionFactory factory) {
RedisSerializationContext<String, Object> context = RedisSerializationContext.newSerializationContext()
.key(new StringRedisSerializer())
.value(new GenericJackson2JsonRedisSerializer())
.build();
return new ReactiveRedisTemplate<>(factory, context);
}
}鉴权过滤器代码实现
创建实现 GlobalFilter 和 Ordered 接口的类。在 filter 方法中从 ServerWebExchange 获取 Cookie,通过 ReactiveRedisOperations 查询 Redis 会话仓库,避免阻塞 IO。
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Autowired
private ReactiveRedisOperations<String, Object> redisOperations;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String sessionId = extractSessionId(request);
if (sessionId == null) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
String sessionKey = "spring:session:sessions:" + sessionId;
return redisOperations.hasKey(sessionKey)
.flatMap(exists -> {
if (Boolean.TRUE.equals(exists)) {
return chain.filter(exchange);
} else {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
});
}
private String extractSessionId(ServerHttpRequest request) {
HttpCookie cookie = request.getCookies().getFirst("SESSION");
return cookie != null ? cookie.getValue() : null;
}
@Override
public int getOrder() {
return -100;
}
}跨域与 Cookie 配置详解
若前端与网关域名不同,需在网关配置中允许 Cookie 跨域携带。除了 YAML 配置外,还需在 Cors 配置中设置 `allowCredentials(true)`。
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}怎么验证是否生效
1. 查看 Redis:使用 `redis-cli keys "spring:session:*"` 命令,登录后应能看到生成的会话键。
2. 检查请求:浏览器发起请求时,确认 Request Headers 中包含 Cookie 字段,且值为 SESSION=xxx。
3. 观察响应:未登录请求应返回 401 Unauthorized,登录后的请求应正常转发至微服务并返回 200。
4. 日志确认:在网关日志中打印过滤器拦截日志,确认会话加载成功或失败的具体原因。
常见坑
1. Cookie 域名不匹配:网关域名与前端页面域名不一致时,浏览器可能拒绝发送 Cookie,需配置 same-site 为 None 且开启 secure。
2. 依赖冲突:引入 spring-session-data-redis 时若不小心带入 spring-boot-starter-web,会导致启动报错,因为 Gateway 不支持 Servlet 容器。
3. 序列化问题:Redis 中存储的会话对象若自定义了属性,需确保网关与认证服务端的序列化配置一致,否则无法读取。
4. 性能损耗:每次请求都访问 Redis 会增加网络开销,高并发场景下建议监控 Redis 延迟,必要时引入本地缓存减轻压力。
参考来源
1. Spring Cloud Gateway 官方项目页,Spring Cloud Gateway,https://spring.io/projects/spring-cloud-gateway
2. Spring Session 官方项目页,Spring Session,https://spring.io/projects/spring-session