Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。Sa-Token 旨在以简单、优雅的方式完成系统的权限认证部分
了解更多:Sa-Token官方文档
<!--如果你使用的是 SpringBoot 3.x,只需要将 sa-token-spring-boot-starter 修改为 sa-token-spring-boot3-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.35.0.RC</version>
</dependency>
server:
# 端口
port: 8081
##############
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
代码如下(示例):
document.querySelector("#but").addEventListener("click",function () {
const request = new XMLHttpRequest;
request.onreadystatechange=function () {
if (request.readyState===4){
if (request.status===200){
document.getElementById("mydiv").innerHTML=eval("("+request.responseText+")")["msg"]
}else {
alert(request.status);
}
}
}
request.open("get","http://localhost:8081/user/doLogin?username=zhang&password=123456",true);
request.send()
})
@GetMapping("doLogin")
public SaResult doLogin(@RequestParam("username")String username,@RequestParam("password")String password) {
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
if("zhang".equals(username) && "123456".equals(password)) { StpUtil.login(10001);
//StpUtil.login(id) 方法利用了 Cookie 自动注入的特性,自动向前端返回token信息
return SaResult.ok("登陆成功");
}
return SaResult.error("登陆失败");
}
更多关于Sa-Token的登陆认证的其他用法,可以查看Sa-Token官方文档
新建一个类,实现 StpInterface接口,例如以下代码:
/**
* 自定义权限加载接口实现类
*/
@Component // 保证此类被 SpringBoot 扫描,完成 Sa-Token 的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
* @param loginId:账号id
* @param loginType:账号体系标识
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本 list 仅做模拟,实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<String>();
list.add("101");
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;
}
}
// 获取:当前账号所拥有的权限集合
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");
@RestControllerAdvice
public class GlobalExceptionHandler {
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}
StpUtil.logout(10001); // 强制指定账号注销下线
StpUtil.logout(10001, "PC"); // 强制指定账号指定端注销下线
StpUtil.logoutByTokenValue("token"); // 强制指定 Token 注销下线
StpUtil.kickout(10001); // 将指定账号踢下线
StpUtil.kickout(10001, "PC"); // 将指定账号指定端踢下线
StpUtil.kickoutByTokenValue("token"); // 将指定 Token 踢下线
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
// 登录校验:只有登录之后才能进入该方法
@SaCheckLogin
@RequestMapping("info")
public String info() {
return "查询用户信息";
}
//写法一:orRole = "admin",代表需要拥有角色 admin 。
//写法二:orRole = {"admin", "manager", "staff"},代表具有三个角色其一即可。
//写法三:orRole = {"admin, manager, staff"},代表必须同时具有三个角色。
// 角色权限双重 “or校验”:具备指定权限或者指定角色即可通过校验
@SaCheckRole(value = "user.add", orRole = "admin")
@RequestMapping("add")
public String add() {
return "用户增加";
}
// 权限校验:必须具有指定权限才能进入该方法
// 注解式鉴权:只要具有其中一个权限即可通过校验
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)
@RequestMapping("add")
public String add() {
return "用户增加";
}
// 二级认证校验:必须二级认证之后才能进入该方法
// 此接口加上了 @SaIgnore 可以游客访问
@SaIgnore
@SaCheckSafe()
@RequestMapping("add")
public String add() {
return "用户增加";
}
// Http Basic 校验:只有通过 Basic 认证后才能进入该方法
@SaCheckBasic(account = "sa:123456")
@RequestMapping("add")
public String add() {
return "用户增加";
}
// 校验当前账号是否被封禁 comment 服务,如果已被封禁会抛出异常,无法进入方法
@SaCheckDisable("comment")
@RequestMapping("send")
public String send() {
return "查询用户信息";
}
// 在 `@SaCheckOr` 中可以指定多个注解,只要当前会话满足其中一个注解即可通过验证,进入方法。
@SaCheckOr(
// 当前客户端只要有 [ login 账号登录] 或者 [user 账号登录] 其一,就可以通过验证进入方法。
login = { @SaCheckLogin(type = "login"), @SaCheckLogin(type = "user") }
role = @SaCheckRole("admin"),
permission = @SaCheckPermission("user.add"),
safe = @SaCheckSafe("update-password"),
basic = @SaCheckBasic(account = "sa:123456"),
disable = @SaCheckDisable("submit-orders")
)
@RequestMapping("test")
public SaResult test() {
// ...
return SaResult.ok();
}
@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");
}
}
@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.stop() 会停止匹配,进入Controller。
SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin")).stop();
//SaRouter.back() 会停止匹配,直接返回结果到前端。
SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods")).back("要返回给前端的内容");
SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
// 进入 free 独立作用域
SaRouter.match("/**").free(r -> {
SaRouter.match("/a/**").check(/* --- */);
SaRouter.match("/b/**").check(/* --- */).stop();
SaRouter.match("/c/**").check(/* --- */);
});
// 执行 stop() 函数跳出 free 后继续执行下面的 match 匹配
SaRouter.match("/**").check(/* --- */);
SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment"));
// 指定关闭掉注解鉴权能力,这样框架就只会做路由拦截校验了
})/*.isAnnotation(false)*/).addPathPatterns("/**");
}
}
在 Sa-Token 中,Session 分为三种,分别是:
// 获取当前账号 id 的 Account-Session (必须是登录后才能调用)
StpUtil.getSession();
// 获取当前账号 id 的 Account-Session, 并决定在 Session 尚未创建时,是否新建并返回
StpUtil.getSession(true);
// 获取账号 id 为 10001 的 Account-Session
StpUtil.getSessionByLoginId(10001);
// 获取账号 id 为 10001 的 Account-Session, 并决定在 Session 尚未创建时,是否新建并返回
StpUtil.getSessionByLoginId(10001, true);
// 获取 SessionId 为 xxxx-xxxx 的 Account-Session, 在 Session 尚未创建时, 返回 null
StpUtil.getSessionBySessionId("xxxx-xxxx");
// 获取当前 Token 的 Token-Session 对象
StpUtil.getTokenSession();
// 获取指定 Token 的 Token-Session 对象
StpUtil.getTokenSessionByToken(token);
以一个特定的值作为 SessionId 来分配的Session
// 查询指定key的Session是否存在
SaSessionCustomUtil.isExists("goods-10001");
// 获取指定key的Session,如果没有,则新建并返回
SaSessionCustomUtil.getSessionById("goods-10001");
// 获取指定key的Session,如果没有,第二个参数决定是否新建并返回
SaSessionCustomUtil.getSessionById("goods-10001", false);
// 删除指定key的Session
SaSessionCustomUtil.deleteSessionById("goods-10001");
// 写值
session.set("name", "zhang");
// 写值 (只有在此key原本无值的时候才会写入)
session.setDefaultValue("name", "zhang");
// 取值
session.get("name");
// 取值 (指定默认值)
session.get("name", "" );
// 取值 (若无值则执行参数方法, 之后将结果保存到此键名下,并返回此结果 若有值则直接返回, 无需执行参数方法)
session.get("name", () -> {
return ...;
});
// ---------- 数据类型转换: ----------
session.getInt("age"); // 取值 (转int类型)
session.getLong("age"); // 取值 (转long类型)
session.getString("name"); // 取值 (转String类型)
session.getDouble("result"); // 取值 (转double类型)
session.getFloat("result"); // 取值 (转float类型)
session.getModel("key", Student.class); // 取值 (指定转换类型)
session.getModel("key", Student.class, <defaultValue>); // 取值 (指定转换类型, 并指定值为Null时返回的默认值)
// 是否含有某个key (返回 true 或 false)
session.has("key");
// 删值
session.delete('name');
// 清空所有值
session.clear();
// 获取此 Session 的所有key (返回Set)
session.keys();
默认场景下,只有登录后才能通过 StpUtil.getTokenSession() 获取 Token-Session。如果想要在未登录场景下获取 Token-Session ,有两种方法:
// 获取当前 Token 的匿名 Token-Session (可在未登录情况下使用的 Token-Session)
StpUtil.getAnonTokenSession();
以上就是springboot整合Sa-token要讲的内容,本文仅仅简单介绍了最基本的使用,如果想继续深入了解Sa-Token,可以查看Sa-Token官方文档。