全部开发者教程

企业级在线办公系统

这门课程中,我们使用到了开源的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要简单多了。