说明: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.Driverurl: jdbc:mysql://IP地址:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: adminpassword: 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 {// 全局异常拦截 @ExceptionHandlerpublic 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 {@Autowiredprivate 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 {@Autowiredprivate UserMapper userMapper;/*** 返回一个账号所拥有的权限码集合 */@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {return userMapper.getPermissionListByLoginId(StpUtil.getLoginIdAsInt());}/*** 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)*/@Overridepublic 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.namefrom i_role_permissions_mapping t1inner join i_user_roles_mapping t2 on t1.role_name = t2.role_nameinner join i_permissions t3 on t3.id = t1.permission_namewhere t2.user_name = #{loginIdAsInt}</select><select id="getRoleListByLoginId" resultType="java.lang.String" parameterType="int">select t2.namefrom i_user_roles_mapping t1inner join i_roles t2 on t2.id = t1.role_namewhere 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 拦截器,打开注解式鉴权功能 @Overridepublic 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 {// 注册拦截器@Overridepublic 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 {@Overridepublic 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文档