在微服务中使用 Sa-Token:分布式Session会话、网关统一鉴权

Sa-Token 是一个轻量级 java 权限认证框架,主要解决登录认证、权限认证、单点登录、OAuth2、微服务网关鉴权 等一系列权限相关问题。

Gitee 开源地址:https://gitee.com/dromara/sa-token

本篇将介绍在微服务中使用 Sa-Token 的两种常见技术:
- 分布式 Session 会话。
- 网关统一鉴权。

一、分布式 Session 会话

微服务架构下的第一个难题便是数据同步,单机版的Session在分布式环境下一般不能正常工作,为此我们需要对框架做一些特定的处理。

首先我们要明白,分布式环境下为什么Session会失效?因为用户在一个节点对会话做出的更改无法实时同步到其它的节点,
这就导致一个很严重的问题:如果用户在节点一上已经登录成功,那么当下一次的请求落在节点二上时,对节点二来讲,此用户仍然是未登录状态。

要怎么解决这个问题呢?目前的主流方案有四种:
1. Session同步:只要一个节点的数据发生了改变,就强制同步到其它所有节点
2. Session粘滞:通过一定的算法,保证一个用户的所有请求都稳定的落在一个节点之上,对这个用户来讲,就好像还是在访问一个单机版的服务
3. 建立会话中心:将Session存储在专业的缓存中间件上,使每个节点都变成了无状态服务,例如:Redis
4. 颁发无状态token:放弃Session机制,将用户数据直接写入到令牌本身上,使会话数据做到令牌自解释,例如:jwt

使用 Sa-Token 可以为项目构建一个会话中心模式,所有的数据都统一放在 Redis 中,每个服务都是无状态服务,保证会话数据的一致性。

动态演示图:

会话中心

在每个服务中引入以下依赖(或根pom统一引入)

<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dao-redis-jackson</artifactId>
    <version>1.34.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如上述依赖所示,只要所有客户端配置一样的 Redis 链接,他们的会话就可以互通。

如果你的业务无法统一配置一个 Redis,也可以使用 Alone-Redis 插件,为 Sa-Token 单独配置一个Redis 依赖,做到权限缓存与业务缓存分离:

<!-- Sa-Token插件:权限缓存与业务缓存分离 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-alone-redis</artifactId>
    <version>1.34.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然后在application.yml中增加配置

# Sa-Token 配置
sa-token: 
    # Token名称
    token-name: satoken
    # Token有效期
    timeout: 2592000
    # Token风格
    token-style: uuid

    # 配置 Sa-Token 单独使用的 Redis 连接 
    alone-redis: 
        # Redis数据库索引(默认为0)
        database: 2
        # Redis服务器地址
        host: 127.0.0.1
        # Redis服务器连接端口
        port: 6379
        # Redis服务器连接密码(默认为空)
        password: 
        # 连接超时时间
        timeout: 10s

spring: 
    # 配置业务使用的 Redis 连接 
    redis: 
        # Redis数据库索引(默认为0)
        database: 0
        # Redis服务器地址
        host: 127.0.0.1
        # Redis服务器连接端口
        port: 6379
        # Redis服务器连接密码(默认为空)
        password: 
        # 连接超时时间
        timeout: 10s
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

新建Controller测试一下:

@RestController
@RequestMapping("/test/")
public class TestController {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    // 测试Sa-Token缓存
    @RequestMapping("login")
    public SaResult login(@RequestParam(defaultValue="10001") String id) {
        System.out.println("--------------- 测试Sa-Token缓存");
        StpUtil.login(id);    
        return SaResult.ok();
    }

    // 测试业务缓存
    @RequestMapping("test")
    public SaResult test() {
        System.out.println("--------------- 测试业务缓存");
        stringRedisTemplate.opsForValue().set("hello", "Hello World");
        return SaResult.ok();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

分别访问两个接口,观察Redis中增加的数据:
Alone-Redis

二、网关统一鉴权

微服务架构下的鉴权一般分为两种:

  • 每个服务各自鉴权。
  • 网关统一鉴权。

方案一和传统单体鉴权差别不大,不再过多赘述,下面介绍方案二的整合步骤:

1、引入正确的依赖:

在交流群里不少小伙伴提出bug解决到最后发现都是因为依赖引入错误导致的,此处重点强调一下:

在微服务架构中使用Sa-Token时,网关和内部服务要分开引入Sa-Token依赖(不要直接在顶级父pom中引入Sa-Token)

总体来讲,我们需要关注的依赖就是两个:sa-token-spring-boot-startersa-token-reactor-spring-boot-starter

<!-- Sa-Token 权限认证 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.34.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
<!-- Sa-Token 权限认证(Reactor响应式集成) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
    <version>1.34.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

至于怎么分辨我们需要引入哪个呢?这个要看你使用的基础框架:

对于内部基础服务来讲,我们一般都是使用SpringBoot默认的web模块:SpringMVC,
因为这个SpringMVC是基于Servlet模型的,在这里我们需要引入的是sa-token-spring-boot-starter

对于网关服务,大体来讲分为两种:
- 一种是基于Servlet模型的,如:Zuul,我们需要引入的是:sa-token-spring-boot-starter,详细戳:在SpringBoot环境集成
- 一种是基于Reactor模型的,如:SpringCloud Gateway,我们需要引入的是:sa-token-reactor-spring-boot-starter

注:切不可直接在一个项目里同时引入这两个依赖,否则会造成项目无法启动。

另外,我们需要引入Redis集成包,因为我们的网关和子服务主要通过Redis来同步数据

<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dao-redis-jackson</artifactId>
    <version>1.34.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2、实现鉴权接口

/**
 * 自定义权限验证接口扩展 
 */
@Component   
public class StpInterfaceImpl implements StpInterface {

    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的权限列表 
        return ...;
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的角色列表
        return ...;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

关于数据的获取,建议以下方案三选一:
1. 在网关处集成ORM框架,直接从数据库查询数据
2. 先从Redis中获取数据,获取不到时走ORM框架查询数据库
3. 先从Redis中获取缓存数据,获取不到时走RPC调用子服务 (专门的权限数据提供服务) 获取

3、注册全局过滤器

然后我们就可以在网关处注册全局过滤器进行鉴权操作了:

/**
 * [Sa-Token 权限认证] 配置类 
 * @author kong
 */
@Configuration
public class SaTokenConfigure {
    // 注册 Sa-Token全局过滤器 
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
            // 拦截地址 
            .addInclude("/**")    /* 拦截全部path */
            // 开放地址 
            .addExclude("/favicon.ico")
            // 鉴权方法:每次访问进入 
            .setAuth(obj -> {
                // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 
                SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin());

                // 权限认证 -- 不同模块, 校验不同权限 
                SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
                SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
                SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
                SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));

                // 更多匹配 ...  */
            })
            // 异常处理方法:每次setAuth函数出现异常时进入 
            .setError(e -> {
                return SaResult.error(e.getMessage());
            })
            ;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

在此处,你可以按照 路由拦截鉴权 模式进行鉴权。

Sa-Token 认证流程图


参考资料

← 前后端分离架构下使用 Sa-Token 完成登录认证 对敏感操作的二次认证 —— 详解 Sa-Token 二级认证 →

当前博客已开放版权,所有人均可免费转载,无需联系 Sa-Token 团队获取授权。只需要在转载时保留底部 Sa-Token 官网链接 + 开源仓库链接即可。

Copyright ©2026 Sa-Token java 权限认证 | sa-token.com | 鲁ICP备18046274号-5 | 鲁公网安备37011202002956号