这门课程中,我们使用到了开源的Sa-Token框架。说到Sa-Token框架,有的同学内心产生怀疑:这个框架能用在实战当中吗?这个尽可放心,用在实战当中毫无压力,而且这个框架一直都在不断的迭代升级,所以功能越来越丰富。其Sa-Token的权限验证与授权过程,与Shiro非常类似,只不过设计的更加简单,直接跟JWT融合在了一起。我们写少量的代码就能把Sa-Token整合到SpringBoot项目中。
Sa-Token框架的开源地址(http://sa-token.dev33.cn/)大家可以用浏览器访问,关于该框架的手册文档可以访问(https://sa-token.cc/doc.html#/ )这个网址
Sa-Token能做什么
- 登录验证 —— 轻松登录鉴权,并提供五种细分场景值
- 权限验证 —— 适配RBAC权限模型,不同角色不同授权
- Session会话 —— 专业的数据缓存中心
- 踢人下线 —— 将违规用户立刻清退下线
- 账号封禁 —— 封禁指定账号,使其无法登陆,还可指定解封时间
- 持久层扩展 —— 可集成Redis、Memcached等专业缓存中间件,重启数据不丢失
- 分布式会话 —— 提供jwt集成和共享数据中心两种分布式会话方案
- 微服务网关鉴权 —— 适配Gateway、Soul、Zuul等常见网关组件的请求拦截认证
- 单点登录 —— 一处登录,处处通行
- 临时Token验证 —— 解决短时间的Token授权问题
- 模拟他人账号 —— 实时操作任意用户状态数据
- 临时身份切换 —— 将会话身份临时切换为其它账号
- 无Cookie模式 —— APP、小程序等前后台分离场景
- 同端互斥登录 —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录
- 多账号认证体系 —— 比如一个商城项目的user表和admin表分开鉴权
- 花式token生成 —— 内置六种token风格,还可自定义token生成策略
- 注解式鉴权 —— 优雅的将鉴权与业务代码分离
- 路由拦截式鉴权 —— 根据路由拦截鉴权,可适配restful模式
- 自动续签 —— 提供两种token过期策略,灵活搭配使用,还可自动续签
- 会话治理 —— 提供方便灵活的会话查询接口
- 记住我模式 —— 适配[记住我]模式,重启浏览器免验证
- 密码加密 —— 提供密码加密模块,可快速MD5、SHA1、SHA256、AES、RSA加密
- 全局侦听器 —— 在用户登陆、注销、被踢下线等关键性操作时进行一些AOP操作
- 开箱即用 —— 提供SpringMVC、WebFlux等常见web框架starter集成包,真正的开箱即用
一、导入依赖库
在pom.xml
文件中,引入相关的依赖库,作用各不相同。
<!--核心库-->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.20.0</version>
</dependency>
<!--用Redis缓存授权信息-->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</artifactId>
<version>1.20.0</version>
</dependency>
<!--注解式权限验证-->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-aop</artifactId>
<version>1.20.0</version>
</dependency>
二、修改配置文件
在application.yml
文件中,定义Sa-Token的配置信息。
sa-token:
#token名称 (同时也是cookie名称)
token-name: token
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# token风格
token-style: uuid
三、权限和角色判定
在本项目中,我们用注解的方式判定用户是否为特定的角色或者拥有某些权限。Sa-Token框架为我们提供了这样的注解。比如说下面的Web方法用到了@SaCheckPermission
注解判断用户是否具备ROOT
或者AMECT:INSERT
权限。
@PostMapping("/insert")
@Operation(summary = "添加罚款记录")
@SaCheckPermission(value = {"ROOT", "AMECT:INSERT"}, mode = SaMode.OR)
public R insert(@Valid @RequestBody InsertAmectForm form) {
……
}
@SaCheckPermission
或者@SaCheckRole
注解拦截HTTP请求的时候,会调用特定的Java类来获取用户的权限和角色信息,然后跟注解要求的权限或者角色做匹配,如果能匹配上,就允许HTTP请求调用Web方法,否则就拒绝HTTP请求。话说回来,查询用户权限和角色的若干Java代码需要我们自己写,下面咱们就先从SQL语句开始。
1. 查询用户的权限信息
我们先来学习一下RBAC权限模型。
RBAC权限模型
RBAC的基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。
RBAC模型中的权限是由模块和行为合并在一起而产生的,在MySQL中,有模块表(tb_module)
和行为表(tb_action)
,这两张表的记录合并在一起就行程了权限记录,保存在权限表(tb_permission)
中。
现在知道了权限记录是怎么来的,下面我们看看怎么把权限关联到角色中。传统一点的做法是创建一个交叉表,记录角色拥有什么权限。但是现在MySQL5.7
之后引入了JSON
数据类型,所以我在角色表(tb_role)
中设置的permissions字段,类型是JSON格式的。
到目前为止,JSON类型已经支持索引机制,所以我们不用担心存放在JSON字段中的数据检索速度慢了。MySQL为JSON类型配备了很多函数,我们可以很方便的读写JSON字段中的数据。
接下来我们看看角色是怎么关联到用户的,其实我在用户表(tb_user)
上面设置role字段,类型依旧是JSON的。这样我就可以把多个角色关联到某个用户身上了。
在TbUserDao.xml
文件中,定义searchUserPermissions
这个SQL语句。
<select id="searchUserPermissions" parameterType="int" resultType="String">
SELECT DISTINCT p.permission_name
FROM tb_user u
JOIN tb_role r ON JSON_CONTAINS(u.role, CAST(r.id AS CHAR))
JOIN tb_permission p ON JSON_CONTAINS(r.permissions, CAST(p.id AS CHAR))
WHERE u.id=#{userId} AND u.status=1;
</select>
在TbUserDao.java
接口中定义Dao方法。
@Mapper
public interface TbUserDao {
……
public Set<String> searchUserPermissions(int userId);
}
2. 创建StpInterfaceImpl类
在com.example.emos.api.config
包中创建StpInterfaceImpl.java
类,这个Java类就是Sa-Token框架拦截HTTP请求之后调用的类。在这个类中,我们一共要声明两个方法分别用来查询用户实际的权限和角色。然后Sa-Token框架的@SaCheckPermission
或者@SaCheckRole
注解会根据查询出来的权限和角色,跟注解要求的权限或者角色做匹配。
package com.example.emos.api.config;
import cn.dev33.satoken.stp.StpInterface;
import com.example.emos.api.db.dao.TbUserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Component
public class StpInterfaceImpl implements StpInterface {
@Autowired
private TbUserDao userDao;
/**
* 返回一个用户所拥有的权限集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginKey) {
int userId = Integer.parseInt(loginId.toString());
Set<String> permissions = userDao.searchUserPermissions(userId);
ArrayList list = new ArrayList();
list.addAll(permissions);
return list;
}
/**
* 返回一个用户所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginKey) {
//因为本项目不需要用到角色判定,所以这里就返回一个空的ArrayList对象
ArrayList<String> list = new ArrayList<String>();
return list;
}
}
好了,至此我们就已经配置好了Sa-Token权限验证框架,比我们自己动手配置Shiro+JWT要简单多了。