SpringCloud Gateway与Sa-Token实现微服务统一鉴权
1. 项目概述SpringCloud Alibaba Gateway与Sa-Token的登录鉴权整合在微服务架构中API网关作为系统入口承担着流量调度和安全防护的双重职责。传统方案中我们常使用Spring Security进行鉴权但其配置复杂度让不少开发者望而生畏。Sa-Token作为一款轻量级Java权限认证框架以注解式鉴权为核心设计理念与SpringCloud Gateway的结合能显著降低统一认证的实现门槛。这次我们要构建的鉴权流程包含三个关键组件Gateway服务作为所有请求的入口负责路由转发和全局过滤Auth服务基于Sa-Token实现登录认证和令牌签发Product服务受保护的资源服务通过注解校验权限提示本方案采用Redis作为会话存储介质确保分布式环境下的会话一致性。实际部署时请确保Redis服务可用。2. 环境准备与基础配置2.1 项目结构规划建议采用以下模块划分springcloud-alibaba ├── gateway-service # 网关模块 ├── auth-service # 认证中心 └── product-service # 资源服务2.2 依赖管理各模块需引入的关键依赖Gateway模块dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-gateway/artifactId /dependency dependency groupIdcn.dev33/groupId artifactIdsa-token-reactor-spring-boot-starter/artifactId version1.34.0/version /dependencyAuth模块dependency groupIdcn.dev33/groupId artifactIdsa-token-spring-boot-starter/artifactId version1.34.0/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency3. 认证中心实现细节3.1 认证核心配置在auth-service中配置application.ymlserver: port: 8101 spring: application: name: service-auth redis: host: 127.0.0.1 port: 6379 timeout: 10s lettuce: pool: max-active: 200 max-idle: 10 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/auth_db?useSSLfalse username: root password: 123456 cloud: nacos: discovery: server-addr: localhost:88483.2 登录接口实现创建AuthController处理认证逻辑RestController RequestMapping(/user) public class AuthController { PostMapping(/doLogin) public JsonResult doLogin(String username, String password) { // 模拟数据库校验 if(zhang.equals(username) 123456.equals(password)){ StpUtil.login(10001); return JsonResult.success(登录成功, StpUtil.getTokenInfo()); } return JsonResult.fail(登录失败); } SaCheckLogin GetMapping(/checkLogin) public JsonResult checkLogin() { return JsonResult.success(已登录, StpUtil.getLoginId()); } }4. 网关层关键实现4.1 全局过滤器配置创建GatewayAuthFilter实现登录校验Component public class GatewayAuthFilter implements GlobalFilter, Ordered { private static final ListString WHITE_LIST Arrays.asList( /auth/user/doLogin, /product-serv/product/list ); Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request exchange.getRequest(); String path request.getURI().getPath(); // 白名单直接放行 if(WHITE_LIST.contains(path)){ return chain.filter(exchange); } // 校验Token有效性 try { String token request.getHeaders().getFirst(Authorization); if(StringUtils.isEmpty(token) || !StpUtil.checkToken(token)){ return unauthorizedResponse(exchange); } return chain.filter(exchange); } catch (Exception e) { return unauthorizedResponse(exchange); } } private MonoVoid unauthorizedResponse(ServerWebExchange exchange) { ServerHttpResponse response exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().add(Content-Type, application/json); return response.writeWith(Mono.just(response.bufferFactory() .wrap(JsonResult.fail(请先登录).toString().getBytes()))); } Override public int getOrder() { return -100; } }4.2 路由配置示例gateway-service的application.ymlspring: cloud: gateway: routes: - id: auth-service uri: lb://service-auth predicates: - Path/auth/** - id: product-service uri: lb://service-product predicates: - Path/product-serv/**5. 资源服务保护方案5.1 注解式权限控制在product-service的Controller中使用Sa-Token注解RestController RequestMapping(/product) public class ProductController { SaCheckLogin GetMapping(/{id}) public Product getById(PathVariable Long id) { // 业务逻辑 } SaCheckRole(admin) PostMapping public Product create(RequestBody Product product) { // 创建逻辑 } }5.2 会话存储验证登录成功后可在Redis中看到类似数据结构sa-token:login:session:10001 - {tokenValue:xxxxx,loginId:10001} sa-token:login:token:xxxxx - 100016. 全链路测试流程6.1 未登录访问测试直接请求商品详情接口GET http://localhost:7000/product-serv/product/1应收到401响应{code:401,msg:请先登录,data:null}6.2 登录流程测试调用登录接口POST http://localhost:7000/auth/user/doLogin?usernamezhangpassword123456响应示例{ code:200, msg:登录成功, data:{ tokenName:satoken, tokenValue:xxxxx, isLogin:true, loginId:10001 } }6.3 已登录访问测试携带Token访问商品接口GET http://localhost:7000/product-serv/product/1 Authorization: satoken xxxxx应正常返回商品数据7. 进阶配置与优化建议7.1 令牌自动续期配置在auth-service中添加配置Configuration public class SaTokenConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/user/doLogin); } PostConstruct public void setSaTokenConfig() { // 令牌有效期30分钟最后5分钟自动续期 SaManager.getConfig() .setTimeout(60 * 30) .setActivityTimeout(60 * 25); } }7.2 微服务间鉴权方案对于服务间调用推荐使用Feign拦截器传递TokenBean public RequestInterceptor requestInterceptor() { return template - { String token StpUtil.getTokenValue(); if(StringUtils.isNotBlank(token)){ template.header(Authorization, satoken token); } }; }8. 常见问题排查指南问题现象可能原因解决方案502 Bad Gateway网关路由配置错误或下游服务不可用检查Nacos服务注册状态验证URI配置401 UnauthorizedToken缺失或失效检查请求头是否携带正确Token验证Redis连接403 Forbidden权限不足检查SaCheckRole注解配置的角色要求跨域问题网关未配置CORS在网关添加CORS过滤器重要提示当出现Token失效但Redis中仍存在会话数据时检查服务器时钟是否同步时差超过3分钟会导致Token校验失败9. 性能优化实践9.1 二级缓存配置在高并发场景下可启用内存缓存减少Redis访问sa-token: is-share: false # 关闭Cookie共享 token-style: uuid # Token生成策略 is-concurrent: true # 开启并发登录 is-share: false is-read-body: false is-read-head: true is-v: false jwt-secret-key: your-secret-key cache: type: redis # 启用二级缓存 second-cache-enable: true second-cache-timeout: 609.2 网关层优化启用响应缓存减少重复鉴权对静态资源路由启用缓存控制头限制频繁登录请求的IPBean public RedisRateLimiter redisRateLimiter() { return new RedisRateLimiter(10, 20); } Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(auth-rate-limit, r - r.path(/auth/**) .filters(f - f.requestRateLimiter(c - c.setRateLimiter(redisRateLimiter()))) .uri(lb://service-auth)) .build(); }在微服务架构演进过程中网关鉴权方案的选择直接影响系统的安全性和开发效率。经过多个项目的实践验证Sa-Token与SpringCloud Gateway的组合在实现成本、维护复杂度和性能表现上达到了较好的平衡。特别是在快速迭代的业务场景中其注解式开发模式能让团队更专注于业务逻辑实现。