Sa-Token 介绍
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。
Sa-Token最新开发文档地址:https://sa-token.cc
Sa-Token功能结构图:
SpringBoot 集成 Sa-Token
-
创建 SpringBoot 项目
-
再 Pom.xml 文件中添加 Sa-Token 依赖
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc --> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot-starter</artifactId> <version>1.34.0</version> </dependency>
注:如果你使用的
SpringBoot 3.x
,只需要将sa-token-spring-boot-starter
修改为sa-token-spring-boot3-starter
即可。 -
设置配置文件
支持零配置启动项目 ,但同时也可以在
application.yml
中增加如下配置。server: # 端口 port: 8081 ############## Sa-Token 配置 (文档: https://sa-token.cc) ############## sa-token: # token名称 (同时也是cookie名称) token-name: satoken # token有效期,单位s 默认30天, -1代表永不过期 timeout: 2592000 # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 activity-timeout: -1 # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) is-concurrent: true # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) is-share: true # token风格 token-style: uuid # 是否输出操作日志 is-log: false
Sa-Token 功能
登录认证
-
会话登录
// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等 StpUtil.login(Object id);
StpUtil.login()
函数让 Sa-Token 为这个账号创建了一个Token凭证,且通过 Cookie 上下文返回给了前端,除此之外,Sa-Token 在背后做了大量的工作,包括但不限于:- 检查此账号是否之前已有登录
- 为账号生成
Token
凭证与Session
会话 - 通知全局侦听器,xx 账号登录成功
- 将
Token
注入到请求上下文 - 等等其它工作……
在 Cookie 功能的加持下,我们可以仅靠
StpUtil.login(id)
一句代码就完成登录认证。- Cookie 可以从后端控制往浏览器中写入 Token 值。
- Cookie 会在前端每次发起请求时自动提交 Token 值。
-
注销登录
// 当前会话注销登录 StpUtil.logout();
-
是否登录
// 获取当前会话是否已经登录,返回true=已登录,false=未登录 StpUtil.isLogin();
-
检查登录
// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`,代表当前会话暂未登录,可能的原因有很多 StpUtil.checkLogin();
会话查询
// 获取当前会话账号id, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.getLoginId();
// 类似查询API还有:
StpUtil.getLoginIdAsString(); // 获取当前会话账号id, 并转化为`String`类型
StpUtil.getLoginIdAsInt(); // 获取当前会话账号id, 并转化为`int`类型
StpUtil.getLoginIdAsLong(); // 获取当前会话账号id, 并转化为`long`类型
// ---------- 指定未登录情形下返回的默认值 ----------
// 获取当前会话账号id, 如果未登录,则返回null
StpUtil.getLoginIdDefaultNull();
// 获取当前会话账号id, 如果未登录,则返回默认值 (`defaultValue`可以为任意类型)
StpUtil.getLoginId(T defaultValue);
Token 查询
// 获取当前会话的token值
StpUtil.getTokenValue();
// 获取当前`StpLogic`的token名称
StpUtil.getTokenName();
// 获取指定token对应的账号id,如果未登录,则返回 null
StpUtil.getLoginIdByToken(String tokenValue);
// 获取当前会话剩余有效期(单位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();
// 获取当前会话的token信息参数
StpUtil.getTokenInfo();
权限认证
因为每个项目的需求不同,其权限设计也千变万化, 所以 Sa-Token 将 [ 获取当前账号权限码集合 ] 操作以接口的方式暴露给你,以方便你根据自己的业务逻辑进行重写。
新建一个类,实现 StpInterface
接口,例如以下代码:
/**
* 自定义权限验证接口扩展
*/
@Component // 保证此类被SpringBoot扫描,完成Sa-Token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<String>();
list.add("user.add");
list.add("user.update");
list.add("user.get");
// list.add("user.delete");
list.add("art.*");
return list;
}
/**
* 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}
参数解释:
- loginId:账号id,即你在调用
StpUtil.login(id)
时写入的标识值。 - loginType:账号体系标识,此处可以暂时忽略,在 [ 多账户认证 ] 章节下会对这个概念做详细的解释。
权限校验
// 获取:当前账号所拥有的权限集合
StpUtil.getPermissionList();
// 判断:当前账号是否含有指定权限, 返回 true 或 false
StpUtil.hasPermission("user.add");
// 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
StpUtil.checkPermission("user.add");
// 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");
// 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");
角色校验
// 获取:当前账号所拥有的角色集合
StpUtil.getRoleList();
// 判断:当前账号是否拥有指定角色, 返回 true 或 false
StpUtil.hasRole("super-admin");
// 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
StpUtil.checkRole("super-admin");
// 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
StpUtil.checkRoleAnd("super-admin", "shop-admin");
// 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
StpUtil.checkRoleOr("super-admin", "shop-admin");
权限通配符(“*”):Sa-Token允许你根据通配符指定泛权限,例如当一个账号拥有
art.*
的权限时,art.add
、art.delete
、art.update
都将匹配通过
注解鉴权
@SaCheckLogin
: 登录校验 —— 只有登录之后才能进入该方法。@SaCheckRole("admin")
: 角色校验 —— 必须具有指定角色标识才能进入该方法。@SaCheckPermission("user:add")
: 权限校验 —— 必须具有指定权限才能进入该方法。@SaCheckSafe
: 二级认证校验 —— 必须二级认证之后才能进入该方法。@SaCheckBasic
: HttpBasic校验 —— 只有通过 Basic 认证后才能进入该方法。@SaIgnore
:忽略校验 —— 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。@SaCheckDisable("comment")
:账号服务封禁校验 —— 校验当前账号指定服务是否被封禁。
Sa-Token 使用全局拦截器完成注解鉴权功能,为了不为项目带来不必要的性能负担,拦截器默认处于关闭状态
因此,为了使用注解鉴权,你必须手动将 Sa-Token 的全局拦截器注册到你项目中
注册 Sa-Token 拦截器
以SpringBoot2.0
为例,新建配置类SaTokenConfigure.java
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
保证此类被springboot
启动类扫描到即可
关闭注解校验
SaInterceptor
只要注册到项目中,默认就会打开注解校验,如果要关闭此能力,需要:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new SaInterceptor(handle -> {
SaRouter.match("/**").check(r -> StpUtil.checkLogin());
}).isAnnotation(false) // 指定关闭掉注解鉴权能力,这样框架就只会做路由拦截校验了
).addPathPatterns("/**");
}
路由拦截鉴权
假设我们有如下需求:
项目中所有接口均需要登录认证,只有 “登录接口” 本身对外开放
那么给每个接口加上鉴权注解?手写全局拦截器?似乎都不是非常方便。
注册 Sa-Token 路由拦截器
以SpringBoot2.0
为例, 新建配置类SaTokenConfigure.java
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.addPathPatterns("/**")
.excludePathPatterns("/user/doLogin");
}
}
以上代码,我们注册了一个基于 StpUtil.checkLogin()
的登录校验拦截器,并且排除了/user/doLogin
接口用来开放登录(除了/user/doLogin
以外的所有接口都需要登录才能访问)。
[记住我] 模式
Sa-Token的登录授权,默认就是[记住我]
模式,为了实现[非记住我]
模式,你需要在登录时如下设置 StpUtil.login()
函数的第二个参数:
// 设置登录账号id为10001,第二个参数指定是否为[记住我],当此值为false后,关闭浏览器后再次打开需要重新登录
StpUtil.login(10001, false);
- 勾选 [记住我] 按钮时:调用
StpUtil.login(10001, true)
,此时用户即使重启浏览器 Token 依然有效。 - 不勾选 [记住我] 按钮时:调用
StpUtil.login(10001, false)
,此时用户在重启浏览器后 Token 便会消失,导致会话失效。
密码加密
Sa-Token
支持摘要加密、对称加密、非对称加密、Base64加密等方式。
// md5加密
SaSecureUtil.md5("123456");
// sha1加密
SaSecureUtil.sha1("123456");
// sha256加密
SaSecureUtil.sha256("123456");
Sa-Token 集成 Redis
方式1、使用 jdk 默认序列化方式
<!-- Sa-Token 整合 Redis (使用 jdk 默认序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</artifactId>
<version>1.34.0</version>
</dependency>
方式2、使用 jackson 序列化方式
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.34.0</version>
</dependency>
集成 Redis 注意
1. 无论使用哪种序列化方式,你都必须为项目提供一个 Redis 实例化方案,例如:
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2. 引入了依赖,我还需要为 Redis 配置连接信息吗?
需要!只有项目初始化了正确的 Redis 实例,Sa-Token
才可以使用 Redis 进行数据持久化,参考以下yml配置
:
spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
# password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
3. 集成 Redis 后,是我额外手动保存数据,还是框架自动保存?
框架自动保存。集成 Redis
只需要引入对应的 pom依赖
即可,框架所有上层 API 保持不变。
4. 集成包版本问题
Sa-Token-Redis 集成包的版本尽量与 Sa-Token-Starter 集成包的版本一致,否则可能出现兼容性问题。
集成 Thymeleaf
可以在 Thymeleaf 页面中使用 Sa-Token 相关API,俗称 —— 标签方言。
1、引入依赖
首先我们确保项目已经引入 Thymeleaf 依赖,然后在此基础上继续添加:
<!-- 在 thymeleaf 标签中使用 Sa-Token -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dialect-thymeleaf</artifactId>
<version>1.34.0</version>
</dependency>
2、注册标签方言对象
在 SaTokenConfigure 配置类中注册 Bean
@Configuration
public class SaTokenConfigure {
// Sa-Token 标签方言 (Thymeleaf版)
@Bean
public SaTokenDialect getSaTokenDialect() {
return new SaTokenDialect();
}
}
3、使用标签方言
然后我们就可以愉快的使用在 Thymeleaf 页面中使用标签方言了
登录判断
<h2>标签方言测试页面</h2>
<p>
登录之后才能显示:
<span sa:login>value</span>
</p>
<p>
不登录才能显示:
<span sa:notLogin>value</span>
</p>
3.2、角色判断
<p>
具有角色 admin 才能显示:
<span sa:hasRole="admin">value</span>
</p>
<p>
同时具备多个角色才能显示:
<span sa:hasRoleAnd="admin, ceo, cto">value</span>
</p>
<p>
只要具有其中一个角色就能显示:
<span sa:hasRoleOr="admin, ceo, cto">value</span>
</p>
<p>
不具有角色 admin 才能显示:
<span sa:lackRole="admin">value</span>
</p>复制到剪贴板错误复制成功12345678910111213141516
权限判断
<p>
具有权限 user-add 才能显示:
<span sa:hasPermission="user-add">value</span>
</p>
<p>
同时具备多个权限才能显示:
<span sa:hasPermissionAnd="user-add, user-delete, user-get">value</span>
</p>
<p>
只要具有其中一个权限就能显示:
<span sa:hasPermissionOr="user-add, user-delete, user-get">value</span>
</p>
<p>
不具有权限 user-add 才能显示:
<span sa:lackPermission="user-add">value</span>
</p>复制到剪贴板错误复制成功12345678910111213141516
调用 Sa-Token 相关API
以上的标签方言,可以满足我们大多数场景下的权限判断,然后有时候我们依然需要更加灵活的在页面中调用 Sa-Token 框架API
首先在 SaTokenConfigure 配置类中为 Thymeleaf 配置全局对象:
public class SaTokenConfigure{
// ... 其它代码
// 为 Thymeleaf 注入全局变量,以便在页面中调用 Sa-Token 的方法
@Autowired
private void configureThymeleafStaticVars(ThymeleafViewResolver viewResolver) {
viewResolver.addStaticVariable("stp", StpUtil.stpLogic);
}
}复制到剪贴板错误复制成功123456789
然后就可以在页面上调用 StpLogic 的 API 了,例如:
<p>调用 StpLogic 方法调用测试</p>
<p th:if="${stp.isLogin()}">
从SaSession中取值:
<span th:text="${stp.getSession().get('name')}"></span>
</p>