sa-token-doc/blog/essays/zai-wei-fu-wu-zhong-shi-yong-sa-token-fen-bu-shi-session-hui-hua-wang-guan-tong.html
2023-04-24随笔
Sa-Token 是一个轻量级 java 权限认证框架,主要解决登录认证、权限认证、单点登录、OAuth2、微服务网关鉴权 等一系列权限相关问题。
Gitee 开源地址:https://gitee.com/dromara/sa-token
本篇将介绍在微服务中使用 Sa-Token 的两种常见技术:
微服务架构下的第一个难题便是数据同步,单机版的Session在分布式环境下一般不能正常工作,为此我们需要对框架做一些特定的处理。
首先我们要明白,分布式环境下为什么Session会失效?因为用户在一个节点对会话做出的更改无法实时同步到其它的节点,
这就导致一个很严重的问题:如果用户在节点一上已经登录成功,那么当下一次的请求落在节点二上时,对节点二来讲,此用户仍然是未登录状态。
要怎么解决这个问题呢?目前的主流方案有四种:
Redisjwt使用 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>
如上述依赖所示,只要所有客户端配置一样的 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>
然后在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
新建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();
}
}
分别访问两个接口,观察Redis中增加的数据:
微服务架构下的鉴权一般分为两种:
方案一和传统单体鉴权差别不大,不再过多赘述,下面介绍方案二的整合步骤:
在交流群里不少小伙伴提出bug解决到最后发现都是因为依赖引入错误导致的,此处重点强调一下:
在微服务架构中使用Sa-Token时,网关和内部服务要分开引入Sa-Token依赖(不要直接在顶级父pom中引入Sa-Token)
总体来讲,我们需要关注的依赖就是两个:sa-token-spring-boot-starter 和 sa-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>
<!-- Sa-Token 权限认证(Reactor响应式集成) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
<version>1.34.0</version>
</dependency>
至于怎么分辨我们需要引入哪个呢?这个要看你使用的基础框架:
对于内部基础服务来讲,我们一般都是使用SpringBoot默认的web模块:SpringMVC,
因为这个SpringMVC是基于Servlet模型的,在这里我们需要引入的是sa-token-spring-boot-starter
对于网关服务,大体来讲分为两种:
sa-token-spring-boot-starter,详细戳:在SpringBoot环境集成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>
/**
* 自定义权限验证接口扩展
*/
@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 ...;
}
}
关于数据的获取,建议以下方案三选一:
然后我们就可以在网关处注册全局过滤器进行鉴权操作了:
/**
* [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());
})
;
}
}
在此处,你可以按照 路由拦截鉴权 模式进行鉴权。
← 前后端分离架构下使用 Sa-Token 完成登录认证对敏感操作的二次认证 —— 详解 Sa-Token 二级认证 →