说明:Sa-Token是一个轻量级java权限认证框架(官方语),所谓权限认证框架,就是登录框架,像Shiro、Spring Security。本文介绍Sa-Token框架的入门使用,基于Spring Boot环境。
准备工作
首先,创建一个简单的Spring Boot项目,pom.xml文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--spring boot-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.12</version>
<relativePath/>
</parent>
<groupId>com.hezy</groupId>
<artifactId>satoken_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Archetype - satoken_demo</name>
<url>http://maven.apache.org</url>
<dependencies>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</project>
application.yml配置文件如下
server:
port: 8080
# 1.数据库配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://IP地址:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: admin
password: MySQL@3306
# 2.mybatis配置
mybatis:
configuration:
# 显示SQL日志配置
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 驼峰命名配置
map-underscore-to-camel-case: true
# 设置mapper.xml文件所在的路径
mapper-locations: classpath:mapper/*.xml
在编码之前,需要介绍一下RBAC(Role-Based Access Control,基于角色的访问控制模型),简单来说,是用户 ⇒ 角色 ⇒ 权限,一个用户有多个角色,一个角色有多个权限。权限对应的是对系统资源的控制,通过角色深度绑定,再将角色分配给用户,非常灵活。对应的数据库设计,就是以下五张表:
-
i_users(用户表);
-
i_roles(角色表);
-
i_permissions(权限表);
-
i_user_roles_mapping(用户角色表);
-
i_role_permissions_mapping(角色权限表);
创建以上五张表,如下:
(i_users,用户表)

(i_roles,角色表)

(i_permissions,权限表)

(i_user_roles_mapping,用户角色表)
以下表示:
-
zhangsan(id为1)的角色是admin、admin-super,拥有这两个角色的权限;
-
lisi(id为2)的角色是admin,拥有admin这个角色的权限;

(i_role_permissions_mapping,角色权限表)
以下表示:
-
admin角色拥有user.add、user.update、user.delete、user.select权限;
-
admin-super角色拥有user.*权限;
-
user角色拥有user.select权限;

下面加入Sa-Token依赖和配置,如下:
<!--sa-token依赖-->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
配置文件
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: true
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true
启动项目,控制台可以看到Sa-Token相关信息

基础使用
使用前,先创建一个全局异常处理器,用来显示Sa-Token返回的错误信息;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}
(1)登录认证
Sa-Token登录认证功能里,只需调用API即可完成登录,发放Token、校验Token都不需要手动编码。如下:
-
StpUtil.login():登录,设置当前用户id;
-
StpUtil.isLogin():判断当前用户是否已登录;
-
StpUtil.getTokenInfo():返回当前用户的Token信息;
-
StpUtil.logout():退出登录;
-
StpUtil.getLoginId():获取当前会话账号id, 如果未登录,则抛出异常:
NotLoginException -
StpUtil.getLoginIdDefaultNull():获取当前会话账号id, 如果未登录,则返回 null;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.hezy.mapper.UserMapper;
import com.hezy.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 登录测试
*/
@RestController
@RequestMapping("/acc/")
public class LoginController {
@Autowired
private UserMapper userMapper;
/**
* 登录
* @param username
* @param password
* @return
*/
@RequestMapping("doLogin")
public SaResult doLogin(String username, String password) {
// 查询用户
User userDO = userMapper.getUserByUsername(username);
// 密码校验
if(userDO.getPassword().equals(password)) {
StpUtil.login(userDO.getId());
return SaResult.ok("登录成功");
}
return SaResult.error("登录失败");
}
/**
* 判断是否登录
* @return
*/
@RequestMapping("isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登录:" + StpUtil.isLogin());
}
/**
* 获取当前登录Token信息
* @return
*/
@RequestMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
/**
* 退出登录
* @return
*/
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}
重启项目,用Apifox测试一下;
(登录)

(当前用户是否已登录)

可以看到,我什么参数都没有传,但是Sa-Token判断已登录,这是因为Apifox有个Cookie管理的功能,会将前面登录返回Token保存下来,后面发的请求自动携带。

(获取当前用户的Token信息,包括了Token的名称、有限期等)

(退出登录)

退出登录后,Apifox里存的Cookie被清空,再访问下判断当前用户是否登录的接口,如下:

(控制台信息)

看下来,是不是非常简单?
(2)权限认证
前面我们创建了一个RBAC模型,可以通过下面的方式来使用。首先,实现Sa-Token的StpInterface接口 ;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import com.hezy.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 自定义权限加载接口实现类
*/
@Component
public class StpInterfaceImpl implements StpInterface {
@Autowired
private UserMapper userMapper;
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return userMapper.getPermissionListByLoginId(StpUtil.getLoginIdAsInt());
}
/**
* 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
return userMapper.getRoleListByLoginId(StpUtil.getLoginIdAsInt());
}
}
对应的Mapper方法
/**
* 根据用户id查询用户的角色
*/
List<String> getRoleListByLoginId(int loginIdAsInt);
/**
* 根据用户id查询该用户的权限
*/
List<String> getPermissionListByLoginId(int loginIdAsInt);
Mapper.xml
<select id="getPermissionListByLoginId" resultType="java.lang.String" parameterType="int">
select t3.name
from i_role_permissions_mapping t1
inner join i_user_roles_mapping t2 on t1.role_name = t2.role_name
inner join i_permissions t3 on t3.id = t1.permission_name
where t2.user_name = #{loginIdAsInt}
</select>
<select id="getRoleListByLoginId" resultType="java.lang.String" parameterType="int">
select t2.name
from i_user_roles_mapping t1
inner join i_roles t2 on t2.id = t1.role_name
where t1.user_name = #{loginIdAsInt}
</select>
(查询当前用户所拥有的角色,及权限)
package com.hezy.controller;
import java.util.List;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 权限认证
*/
@RestController
@RequestMapping("/jur/")
public class JurAuthController {
/**
* 查询当前用户的角色、权限信息
*/
@RequestMapping("getPermission")
public SaResult getPermission() {
// 查询权限信息 ,如果当前会话未登录,会返回一个空集合
List<String> permissionList = StpUtil.getPermissionList();
System.out.println("当前登录账号拥有的所有权限:" + permissionList);
// 查询角色信息 ,如果当前会话未登录,会返回一个空集合
List<String> roleList = StpUtil.getRoleList();
System.out.println("当前登录账号拥有的所有角色:" + roleList);
// 返回给前端
return SaResult.ok()
.set("roleList", roleList)
.set("permissionList", permissionList);
}
}

这样,就可以使用Sa-Token对应的API,来返回当前用户是否拥有某个权限、某个角色,相关API如下:
-
StpUtil.getPermissionList():获取当前用户的所有权限列表;
-
StpUtil.getRoleList():获取当前用户的所有角色列表;
-
StpUtil.hasPermission(“user.add”):判断当前用户是否有“user.add”权限;
-
StpUtil.hasPermissionAnd(“user.add”, “user.delete”, “user.select”):判断当前用户是否有这三个权限,需全部满足;
-
StpUtil.hasPermissionOr(“user.add”, “user.delete”, “user.select”):判断当前用户是否有这三个其中一个权限;
-
StpUtil.checkPermission(“user.add”):校验当前用户是否有“user.add”权限;
-
StpUtil.checkPermissionAnd(“user.add”, “user.delete”, “user.select”);:校验当前用户是否有这三个权限,需全部满足;
-
StpUtil.checkPermissionOr(“user.add”, “user.delete”, “user.select”):校验当前用户是否有这三个其中一个权限;
-
StpUtil.hasRole(“admin”):判断当前用户是否有“admin”角色;
-
StpUtil.hasRoleAnd(“admin”, “ceo”, “cfo”):判断当前用户是否有这三个角色,需全部满足;
-
StpUtil.hasRoleOr(“admin”, “ceo”, “cfo”):判断当前用户是否有这三个其中一个角色;
-
StpUtil.checkRole(“admin”):校验当前用户是否有“admin”角色;
-
StpUtil.checkRoleAnd(“admin”, “ceo”, “cfo”):校验当前用户是否有这三个角色,需全部满足;
-
StpUtil.checkRoleOr(“admin”, “ceo”, “cfo”):校验当前用户是否有这三个其中一个角色;
判断会返回true或false,校验当不满足时,会抛出NotPermissionException(没有权限)或 NotRoleException(没有角色)的异常。另外,Sa-Token还支持用通配符来表示,如下,

我们给admin-super角色分配了一个user.*的权限,当当前用户拥有admin-super角色时,等于拥有所有的user操作权限,以下API均能通过;
StpUtil.hasPermission("user.add");
StpUtil.hasPermissionAnd("user.add", "user.delete", "user.select");
StpUtil.hasPermissionOr("user.add", "user.delete", "user.select");
StpUtil.checkPermission("user.add");
StpUtil.checkPermissionAnd("user.add", "user.delete", "user.select");
StpUtil.checkPermissionOr("user.add", "user.delete", "user.select");
角色校验也能使用通配符,另外通配符还能放在中间,如:system.*.user,表示任意以system开头,以user结尾的权限都能通过校验;
(3)踢人下线
踢人下线相关API如下,可指定需要下线的loginId、token值,或使用loginId+登录类型;
StpUtil.kickout(10001); // 将指定账号踢下线
StpUtil.kickout(10001, "PC"); // 将指定账号指定端踢下线
StpUtil.kickoutByTokenValue("token"); // 将指定 Token 踢下线
测试一下
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token 权限认证示例
*/
@RestController
@RequestMapping("/kickout/")
public class KickoutController {
// 将指定账号踢下线
@RequestMapping("kickout")
public SaResult kickout(long userId) {
// 踢人下线不会清除Token信息,而是将其打上特定标记,再次访问会提示:Token已被踢下线。
StpUtil.kickout(userId);
// 返回
return SaResult.ok();
}
}
(踢人下线)

(再次访问查询该用户角色、权限接口,提示被踢下线)

(4)注解鉴权
Sa-Token也能像Spring Security那样,使用注解来对接口进行权限校验。需创建一个Sa-Token拦截器,如下:
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
然后,可以在需要校验的接口上打上相应的注解,如下:
-
@SaCheckLogin:需要登录才能进入;
-
@SaCheckPermission(“user.add”):需要有“user.add”权限才能进入;
-
@SaCheckPermission(value = {“user.add”, “user.delete”, “user.update”}, mode = SaMode.AND):需要满足这三个权限才能进入;
-
@SaCheckPermission(value = {“user.add”, “user.delete”, “user.update”}, mode = SaMode.OR):需要满足其中一个权限才能进入;
-
@SaCheckRole(“super-admin”):需要拥有“super-admin”角色才能进入;
-
@SaCheckPermission(value = “user.add”, orRole = “admin”):需要具备 "user.add"权限 或者 "admin"角色才能进入
-
@SaIgnore:忽略校验,可直进入;
另外,可使用组合校验,官网的开发文档中提供了一个样例,如下:
// 在 `@SaCheckOr` 中可以指定多个注解,只要当前会话满足其中一个注解即可通过验证,进入方法。
@SaCheckOr(
login = @SaCheckLogin,
role = @SaCheckRole("admin"),
permission = @SaCheckPermission("user.add"),
safe = @SaCheckSafe("update-password"),
basic = @SaCheckBasic(account = "sa:123456"),
disable = @SaCheckDisable("submit-orders")
)
(5)路由拦截鉴权
当我们需要根据接口地址来进行拦截时,可在前面的Sa-Token拦截器中设置,如下:
@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");
}
}
表示,排除地址为“/user/doLogin”接口,其他接口都拦截。另外还可在这里写丰富的拦截逻辑,如下:
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,定义详细认证规则
registry.addInterceptor(new SaInterceptor(handler -> {
// 指定一条 match 规则
SaRouter
.match("/**") // 拦截的 path 列表,可以写多个 */
.notMatch("/user/doLogin") // 排除掉的 path 列表,可以写多个
.check(r -> StpUtil.checkLogin()); // 要执行的校验动作,可以写完整的 lambda 表达式
// 根据路由划分模块,不同模块不同鉴权
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"));
SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment"));
})).addPathPatterns("/**");
}
}
小结
以上是Sa-Token框架的入门使用,我们完全可以基于此框架,将登录业务代码填充到这些API之间,或者将这些API插入到我们开发完成的登录代码中,完成系统的登录功能,可以说非常方便啦。
总结
本文介绍了Sa-Token框架的入门使用,对应Sa-Token开发文档中的基础部分,详细的功能强烈建议参看Sa-Tokan文档
