欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > Sa-Token

Sa-Token

2025/4/19 14:16:55 来源:https://blog.csdn.net/m0_63497607/article/details/147077165  浏览:    关键词:Sa-Token

文章目录

  • 框架介绍
  • Demo 示例
  • 登录认证
    • 登录与注销方法
    • 会话查询
    • token查询
    • Demo
  • 权限认证
    • 获取当前权限码集合
    • 权限校验
    • 角色校验
    • 拦截全局异常
    • SaResult 返回结果封装类源码
    • 权限通配符
    • 如何将权限精确到按钮级
  • 后续内容

框架介绍


轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。


Demo 示例

创建一个 Spring 3 + 的 工程,引入依赖

<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot3-starter</artifactId><version>1.41.0</version></dependency>

创建一个Controller

@RestController
@RequestMapping("/user/")
public class UserController {// 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456@RequestMapping("doLogin")public String doLogin(String username, String password) {// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对 if("zhang".equals(username) && "123456".equals(password)) {StpUtil.login(10001);return "登录成功";}return "登录失败";}// 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin@RequestMapping("isLogin")public String isLogin() {return "当前会话是否登录:" + StpUtil.isLogin();}}

执行结果:
在这里插入图片描述

在这里插入图片描述


登录认证


登录与注销方法

// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等
StpUtil.login(Object id);     

官网说,这个函数的流程大概是:

  • 检查账号是否已登录 (使用这个函数说明用户名密码比对正确)
  • 生成Token 与 Session 会话
  • 记录Token活跃时间
  • 通知全局侦听器,xx账号登陆成功
  • 将Token请求注入请求上下文等

但是我们不需要了解,只需要记住 : Sa-Token 为这个账号创建了Token,并且通过Cookie返回给前端。

登陆接口Demo

// 会话登录接口 
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {// 第一步:比对前端提交的账号名称、密码if("zhang".equals(name) && "123456".equals(pwd)) {// 第二步:根据账号id,进行登录 StpUtil.login(10001);return SaResult.ok("登录成功");}return SaResult.error("登录失败");
}

官网说,这里的login函数利用了 Cookie自动注入的特性,省略了手写token返回代码的过程。

然后给出了一个Cookie要点:

  • Cookie 可以从后端向浏览器中写入 token 值
  • Cookie 会在前端每次发起请求时 自动提交 token值
    在这里插入图片描述

除此之外还有一些方法

// 当前会话注销登录
StpUtil.logout();// 获取当前会话是否已经登录,返回true=已登录,false=未登录
StpUtil.isLogin();// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.checkLogin();

会话查询

// 获取当前会话账号id, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.getLoginId();// 类似查询API还有:
StpUtil.getLoginIdAsString();    // 获取当前会话账号id, 并转化为`String`类型
StpUtil.getLoginIdAsInt();       // 获取当前会话账号id, 并转化为`int`类型
StpUtil.getLoginIdAsLong();      // 获取当前会话账号id, 并转化为`long`类型// ---------- 指定未登录情形下返回的默认值 ----------// 获取当前会话账号id, 如果未登录,则返回 null 
StpUtil.getLoginIdDefaultNull();// 获取当前会话账号id, 如果未登录,则返回默认值 (`defaultValue`可以为任意类型)
StpUtil.getLoginId(T defaultValue);

token查询

// 获取当前会话的 token 值
StpUtil.getTokenValue();// 获取当前`StpLogic`的 token 名称
StpUtil.getTokenName();// 获取指定 token 对应的账号id,如果未登录,则返回 null
StpUtil.getLoginIdByToken(String tokenValue);// 获取当前会话剩余有效期(单位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();// 获取当前会话的 token 信息参数
StpUtil.getTokenInfo();// Sa-TokenInfo
{"code": 200,"msg": "ok","data": {"tokenName": "satoken",           // token名称"tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633",      // token值"isLogin": true,                  // 此token是否已经登录"loginId": "10001",               // 此token对应的LoginId,未登录时为null"loginType": "login",              // 账号类型标识"tokenTimeout": 2591977,          // token剩余有效期 (单位: 秒)"sessionTimeout": 2591977,        // Account-Session剩余有效时间 (单位: 秒)"tokenSessionTimeout": -2,        // Token-Session剩余有效时间 (单位: 秒) (-2表示系统中不存在这个缓存)"tokenActiveTimeout": -1,         // token 距离被冻结还剩的时间 (单位: 秒)"loginDevice": "DEF"   // 登录设备类型 },
}

Demo

/*** 登录测试 */
@RestController
@RequestMapping("/acc/")
public class LoginController {// 测试登录  ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456@RequestMapping("doLogin")public SaResult doLogin(String name, String pwd) {// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对 if("zhang".equals(name) && "123456".equals(pwd)) {StpUtil.login(10001);return SaResult.ok("登录成功");}return SaResult.error("登录失败");}// 查询登录状态  ---- http://localhost:8081/acc/isLogin@RequestMapping("isLogin")public SaResult isLogin() {return SaResult.ok("是否登录:" + StpUtil.isLogin());}// 查询 Token 信息  ---- http://localhost:8081/acc/tokenInfo@RequestMapping("tokenInfo")public SaResult tokenInfo() {return SaResult.data(StpUtil.getTokenInfo());}// 测试注销  ---- http://localhost:8081/acc/logout@RequestMapping("logout")public SaResult logout() {StpUtil.logout();return SaResult.ok();}}

权限认证


获取当前权限码集合

由于每个项目的权限设计不同,Sa-Token将该操作以接口的形式暴露,我们可以通过 实现 StpInterface 接口 自定义权限验证扩展

/*** 自定义权限加载接口实现类*/
@Component    // 保证此类被 SpringBoot 扫描,完成 Sa-Token 的自定义权限验证扩展 
public class StpInterfaceImpl implements StpInterface {/*** 返回一个账号所拥有的权限码集合 */@Overridepublic 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;}/*** 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)*/@Overridepublic 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");    

NotPermissionException 对象可通过 getLoginType() 方法获取具体是哪个 StpLogic 抛出的异常

角色校验

// 获取:当前账号所拥有的角色集合
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");        

NotRoleException 对象可通过 getLoginType() 方法获取具体是哪个 StpLogic 抛出的异常

拦截全局异常

SpringMVC中的全局异常处理器 Demo

@RestControllerAdvice
public class GlobalExceptionHandler {// 全局异常拦截 @ExceptionHandlerpublic SaResult handlerException(Exception e) {e.printStackTrace(); //获取异常信息return SaResult.error(e.getMessage()); // 返回结果}
}

SaResult 返回结果封装类源码

/** Copyright 2020-2099 sa-token.cc** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package cn.dev33.satoken.util;import cn.dev33.satoken.SaManager;import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;/*** 对请求接口返回 Json 格式数据的简易封装。** <p>*     所有预留字段:<br>* 		code = 状态码 <br>* 		msg  = 描述信息 <br>* 		data = 携带对象 <br>* </p>** @author click33* @since 1.22.0*/
public class SaResult extends LinkedHashMap<String, Object> implements Serializable{// 序列化版本号private static final long serialVersionUID = 1L;// 预定的状态码public static final int CODE_SUCCESS = 200;		public static final int CODE_ERROR = 500;		/*** 构建 */public SaResult() {}/*** 构建 * @param code 状态码* @param msg 信息* @param data 数据 */public SaResult(int code, String msg, Object data) {this.setCode(code);this.setMsg(msg);this.setData(data);}/*** 根据 Map 快速构建 * @param map / */public SaResult(Map<String, ?> map) {this.setMap(map);}/*** 获取code * @return code*/public Integer getCode() {return (Integer)this.get("code");}/*** 获取msg* @return msg*/public String getMsg() {return (String)this.get("msg");}/*** 获取data* @return data */public Object getData() {return this.get("data");}/*** 给code赋值,连缀风格* @param code code* @return 对象自身*/public SaResult setCode(int code) {this.put("code", code);return this;}/*** 给msg赋值,连缀风格* @param msg msg* @return 对象自身*/public SaResult setMsg(String msg) {this.put("msg", msg);return this;}/*** 给data赋值,连缀风格* @param data data* @return 对象自身*/public SaResult setData(Object data) {this.put("data", data);return this;}/*** 写入一个值 自定义key, 连缀风格* @param key key* @param data data* @return 对象自身 */public SaResult set(String key, Object data) {this.put(key, data);return this;}/*** 获取一个值 根据自定义key * @param <T> 要转换为的类型 * @param key key* @param cs 要转换为的类型 * @return 值 */public <T> T get(String key, Class<T> cs) {return SaFoxUtil.getValueByType(get(key), cs);}/*** 写入一个Map, 连缀风格* @param map map * @return 对象自身 */public SaResult setMap(Map<String, ?> map) {for (String key : map.keySet()) {this.put(key, map.get(key));}return this;}/*** 写入一个 json 字符串, 连缀风格* @param jsonString json 字符串* @return 对象自身*/public SaResult setJsonString(String jsonString) {Map<String, Object> map = SaManager.getSaJsonTemplate().jsonToMap(jsonString);return setMap(map);}/*** 移除默认属性(code、msg、data), 连缀风格* @return 对象自身*/public SaResult removeDefaultFields() {this.remove("code");this.remove("msg");this.remove("data");return this;}/*** 移除非默认属性(code、msg、data), 连缀风格* @return 对象自身*/public SaResult removeNonDefaultFields() {for (String key : this.keySet()) {if("code".equals(key) || "msg".equals(key) || "data".equals(key)) {continue;}this.remove(key);}return this;}// ============================  静态方法快速构建  ==================================// 构建成功public static SaResult ok() {return new SaResult(CODE_SUCCESS, "ok", null);}public static SaResult ok(String msg) {return new SaResult(CODE_SUCCESS, msg, null);}public static SaResult code(int code) {return new SaResult(code, null, null);}public static SaResult data(Object data) {return new SaResult(CODE_SUCCESS, "ok", data);}// 构建失败public static SaResult error() {return new SaResult(CODE_ERROR, "error", null);}public static SaResult error(String msg) {return new SaResult(CODE_ERROR, msg, null);}// 构建指定状态码 public static SaResult get(int code, String msg, Object data) {return new SaResult(code, msg, data);}// 构建一个空的public static SaResult empty() {return new SaResult();}/* (non-Javadoc)* @see java.lang.Object#toString()*/@Overridepublic String toString() {return "{"+ "\"code\": " + this.getCode()+ ", \"msg\": " + transValue(this.getMsg()) + ", \"data\": " + transValue(this.getData()) + "}";}/*** 转换 value 值:* 	如果 value 值属于 String 类型,则在前后补上引号* 	如果 value 值属于其它类型,则原样返回** @param value 具体要操作的值* @return 转换后的值*/private String transValue(Object value) {if(value == null) {return null;}if(value instanceof String) {return "\"" + value + "\"";}return String.valueOf(value);}}

权限通配符

当一个账号拥有art.*的权限时,art.add、art.delete、art.update都将匹配通过

当一个账号拥有 “*” 权限时,他可以验证通过任何权限码 (角色认证同理)

// 当拥有 art.* 权限时
StpUtil.hasPermission("art.add");        // true
StpUtil.hasPermission("art.update");     // true
StpUtil.hasPermission("goods.add");      // false// 当拥有 *.delete 权限时
StpUtil.hasPermission("art.delete");      // true
StpUtil.hasPermission("user.delete");     // true
StpUtil.hasPermission("user.update");     // false// 当拥有 *.js 权限时
StpUtil.hasPermission("index.js");        // true
StpUtil.hasPermission("index.css");       // false
StpUtil.hasPermission("index.html");      // false

如何将权限精确到按钮级

如此精确的范围控制只依赖后端已经难以完成,此时需要前端进行一定的逻辑判断。

如果是前后端一体项目,可以参考:Thymeleaf标签方言,如果是前后端分离项目,则:

1.登陆时,把当前帐号拥有的所有权限码返回给前端
2.前端将权限码集合保存在localStroage 或者 其它全局状态管理对象中。
3.在需要权限控制的按钮上,使用js进行逻辑判断,例如Vue框架中:

// `arr`是当前用户拥有的权限码数组
// `user.delete`是显示按钮需要拥有的权限码
// `删除按钮`是用户拥有权限码才可以看到的内容。
<button v-if="arr.indexOf('user.delete') > -1">删除按钮</button>

前端的鉴权只是一个辅助功能,为了保证服务器安全:无论前端是否进行了权限校验,后端接口都需要对会话请求再次进行权限校验

后续内容

强烈建议官网查看,非常详细而且通俗易懂还提供了gif动图

Sa-Token官网

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词