1. Spring Cloud Gateway与Sa-Token集成方案解析
在微服务架构中,API网关作为流量入口承担着重要的安全管控职责。最近在重构公司支付系统的网关层时,我选择了Spring Cloud Gateway作为技术栈,并集成轻量级权限框架Sa-Token来实现统一认证鉴权。这种组合既保持了Spring Cloud生态的原生兼容性,又通过Sa-Token的简洁API实现了RBAC权限控制。下面分享具体实现过程中积累的实战经验。
技术选型背景:相比传统Shiro+JWT方案,Sa-Token提供开箱即用的会话管理、踢人下线、权限缓存等特性,且与Spring Cloud Gateway的响应式编程模型完美适配。
2. 核心组件与版本选择
2.1 基础环境配置
<!-- pom.xml关键依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>3.1.3</version> </dependency> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-reactor-spring-boot-starter</artifactId> <version>1.34.0</version> </dependency> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-redis-jackson</artifactId> <version>1.34.0</version> </dependency>2.2 版本匹配原则
- Spring Cloud 2021.0.x(对应Spring Boot 2.6.x)
- Sa-Token 1.34.0+(必须包含reactor适配模块)
- Redis 6.x(推荐使用Jackson序列化方案)
3. 网关认证鉴权实现
3.1 全局过滤器配置
@Component public class SaTokenFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); // 1. 白名单路径直接放行 if(isWhiteList(request.getPath().toString())){ return chain.filter(exchange); } // 2. 执行Sa-Token登录校验 return StpReactorUtil.checkLogin() .flatMap(loginId -> { // 3. 权限校验 String apiPath = request.getPath().toString(); if(!StpInterfaceUtil.hasPermission(loginId, apiPath)){ return Mono.error(new RuntimeException("无访问权限")); } // 4. 请求头注入用户信息 ServerHttpRequest newRequest = request.mutate() .header("X-User-Id", loginId) .build(); return chain.filter(exchange.mutate().request(newRequest).build()); }) .onErrorResume(e -> { // 统一异常处理 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); }); } @Override public int getOrder() { return -100; } }3.2 关键配置参数
# application.yml sa-token: is-concurrent: true # 开启多端登录 is-share: false # 禁止token共享 timeout: 86400 # token有效期(秒) activity-timeout: -1 # 无操作续期 token-style: uuid # token生成策略 redis-database: 1 # 独立Redis库4. 权限数据动态加载方案
4.1 实现StpInterface接口
@Component public class StpInterfaceImpl implements StpInterface { @Autowired private PermissionService permissionService; @Override public List<String> getPermissionList(Object loginId, String loginType) { // 从数据库或缓存加载权限标识 return permissionService.getUserPermissions(loginId.toString()); } @Override public List<String> getRoleList(Object loginId, String loginType) { // 角色验证逻辑 return Collections.emptyList(); } }4.2 权限缓存策略
- 首次请求时加载权限到Redis(TTL=2小时)
- 权限变更时通过Redis Pub/Sub通知网关更新
- 本地JVM缓存作为二级缓存(Caffeine 1000条)
5. 生产环境踩坑实录
5.1 Cookie跨域问题
当网关与前端分离部署时,需要特殊处理:
@Configuration public class CorsConfig { @Bean public WebFilter corsFilter() { return (exchange, chain) -> { ServerHttpResponse response = exchange.getResponse(); response.getHeaders().add("Access-Control-Allow-Credentials", "true"); response.getHeaders().add("Access-Control-Allow-Origin", exchange.getRequest().getHeaders().getOrigin()); return chain.filter(exchange); }; } }5.2 文件上传校验
需绕过Sa-Token的body读取限制:
@Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("file-upload", r -> r.path("/api/upload/**") .filters(f -> f.filter(new SaTokenFilter(), -200)) .uri("lb://file-service")) .build(); }6. 性能优化方案
6.1 基准测试数据
| 场景 | QPS | 平均延迟 | 99线 |
|---|---|---|---|
| 纯网关转发 | 12500 | 12ms | 25ms |
| 网关+基础认证 | 8600 | 18ms | 35ms |
| 网关+完整权限校验 | 5200 | 28ms | 58ms |
6.2 优化措施
- 启用Sa-Token的注解缓存:
@SaCheckPermission(value = "user:add", orRole = "admin") @PostMapping("/user") public Mono<Void> addUser(...) {...}- 路由级权限预加载:
@PostConstruct public void initRoutePermissions() { // 启动时预加载路由-权限映射 routeDefinitionLocator.getRoutes() .flatMap(route -> permissionService.loadRoutePermissions(route.getId())) .subscribe(); }- 响应式Redis连接池配置:
spring: redis: lettuce: pool: max-active: 32 max-idle: 16 min-idle: 87. 安全加固建议
- Token盗用防护:
- 开启Token绑定设备特征
StpUtil.bindDevice("PC-001");- 关键操作二次验证
@SaCheckSafe("write") @PostMapping("/transfer")- 日志审计配置:
@Bean public SaLogTemplate logTemplate() { return new SaLogTemplate() { @Override public void loginLog(String loginType, Object loginId) { log.info("用户{}登录成功,设备:{}", loginId, SaHolder.getRequest().getHeader("User-Agent")); } }; }这套方案在电商系统中实际承载日均300万+请求量,通过灰度发布验证了稳定性。对于需要快速实现微服务鉴权的团队,Spring Cloud Gateway+Sa-Token的组合确实能显著降低开发成本。