官方文档:http://sa-token.dev33.cn/
1 快速开始
整合示例在官方仓库的/sa-token-demo/sa-token-demo-springboot
文件夹下,如遇到难点可结合源码进行学习测试。
https://github.com/dromara/sa-token
1.1 创建项目
在 IDE 中新建一个 SpringBoot 项目,例如:sa-token-demo-springboot
1.2 添加依赖
在项目中直接通过 pom.xml
引入 Sa-Token 的依赖即可
1 2 3 4 5 6
| <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot-starter</artifactId> <version>1.30.0</version> </dependency>
|
1.3 设置配置文件
可以零配置启动项目 ,但同时也可以在 application.yml
中增加如下配置,定制性使用框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| server: port: 8081
sa-token: token-name: satoken timeout: 2592000 activity-timeout: -1 is-concurrent: true is-share: false token-style: uuid is-log: false
|
1.4 创建项目
在项目中新建包 com.pj
,在此包内新建主类 SaTokenDemoApplication.java
1 2 3 4 5 6 7
| @SpringBootApplication public class SaTokenDemoApplication { public static void main(String[] args) throws JsonProcessingException { SpringApplication.run(SaTokenDemoApplication.class, args); System.out.println("启动成功:Sa-Token配置如下:" + SaManager.getConfig()); } }
|
1.5 创建测试Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @RestController @RequestMapping("/user") public class UserController {
@RequestMapping("/doLogin") public String doLogin(String username, String password) { if("zhang".equals(username) && "123456".equals(password)) { StpUtil.login(10001); return "登录成功"; } return "登录失败"; }
@RequestMapping("/isLogin") public String isLogin() { return "当前会话是否登录:" + StpUtil.isLogin(); } }
|
2 登录认证
2.1 设计思路
增加一层接口校验:
- 如果校验通过,则:正常返回数据。
- 如果校验未通过,则:抛出异常,告知其需要先进行登录。
登录访问流程
- 用户提交
name
+ password
参数,调用登录接口。
- 登录成功,返回这个用户的 Token 会话凭证。
- 用户后续的每次请求,都携带上这个 Token。
- 服务器根据 Token 判断此会话是否登录成功。
2.2 登陆与注销
登录
StpUtil.login(Object id);
会话登录
参数填写要登录的账号id,建议的数据类型:long | int | String,不可以传入复杂类型,如:User、Admin 等等
Sa-Token 为这个账号创建了一个Token凭证,且通过 Cookie 上下文返回给了前端,利用了 Cookie 自动注入的特性,省略了手写返回 Token 的代码。
Cookie基本功能:
- Cookie 可以从后端控制往浏览器中写入 Token 值。
- Cookie 会在每次请求时自动提交 Token 值。
注销
StpUtil.logout();
当前会话注销登录
登陆状态
StpUtil.isLogin();
获取当前会话是否已经登录,返回true=已登录,false=未登录
StpUtil.checkLogin();
检验当前会话是否已经登录, 如果未登录,则抛出异常:NotLoginException
2.3 会话查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| StpUtil.getLoginId();
StpUtil.getLoginIdAsString(); StpUtil.getLoginIdAsInt(); StpUtil.getLoginIdAsLong();
StpUtil.getLoginIdDefaultNull();
StpUtil.getLoginId(T defaultValue);
|
2.4 Token查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| StpUtil.getTokenValue();
StpUtil.getTokenName();
StpUtil.getLoginIdByToken(String tokenValue);
StpUtil.getTokenTimeout();
StpUtil.getTokenInfo();
|
TokenInfo详解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "code": 200, "msg": "ok", "data": { "tokenName": "satoken", "tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633", "isLogin": true, "loginId": "10001", "loginType": "login", "tokenTimeout": 2591977, "sessionTimeout": 2591977, "tokenSessionTimeout": -2, "tokenActivityTimeout": -1, "loginDevice": "default-device" }, }
|
2.5 示例
新建 LoginController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
@RestController @RequestMapping("/acc/") public class LoginController {
@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("登录失败"); }
@RequestMapping("isLogin") public SaResult isLogin() { return SaResult.ok("是否登录:" + StpUtil.isLogin()); }
@RequestMapping("tokenInfo") public SaResult tokenInfo() { return SaResult.data(StpUtil.getTokenInfo()); }
@RequestMapping("logout") public SaResult logout() { StpUtil.logout(); return SaResult.ok(); }
}
|
3 权限认证
3.1 设计思路
核心逻辑就是判断一个账号是否拥有指定权限
深入到底层数据中,就是每个账号都会拥有一个权限码集合,框架来校验这个集合中是否包含指定的权限码。
例如:当前账号拥有权限码集合 ["user-add", "user-delete", "user-get"]
,这时候我来校验权限 "user-update"
,则其结果就是:验证失败,禁止访问。
3.2 获取当前账号权限码集合
新建一个类,实现 StpInterface
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
@Component public class StpInterfaceImpl implements StpInterface {
@Override public List<String> getPermissionList(Object loginId, String loginType) { List<String> list = new ArrayList<String>(); list.add("101"); list.add("user-add"); list.add("user-delete"); list.add("user-update"); list.add("user-get"); list.add("article-get"); return list; }
@Override public List<String> getRoleList(Object loginId, String loginType) { List<String> list = new ArrayList<String>(); list.add("admin"); list.add("super-admin"); return list; }
}
|
3.3 权限认证
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| StpUtil.getPermissionList();
StpUtil.hasPermission("user-update");
StpUtil.checkPermission("user-update");
StpUtil.checkPermissionAnd("user-update", "user-delete");
StpUtil.checkPermissionOr("user-update", "user-delete");
|
扩展:NotPermissionException
对象可通过 getLoginType()
方法获取具体是哪个 StpLogic
抛出的异常
3.4 角色认证
在Sa-Token中,角色和权限可以独立验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| StpUtil.getRoleList();
StpUtil.hasRole("super-admin");
StpUtil.checkRole("super-admin");
StpUtil.checkRoleAnd("super-admin", "shop-admin");
StpUtil.checkRoleOr("super-admin", "shop-admin");
|
扩展:NotRoleException
对象可通过 getLoginType()
方法获取具体是哪个 StpLogic
抛出的异常
3.5 拦截全局异常
创建一个全局异常拦截器,统一返回给前端的格式
1 2 3 4 5 6 7 8 9
| @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler public SaResult handlerException(Exception e) { e.printStackTrace(); return SaResult.error(e.getMessage()); } }
|
3.6 权限通配符
Sa-Token允许你根据通配符指定泛权限,例如当一个账号拥有user*
的权限时,user-add
、user-delete
、user-update
都将匹配通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| StpUtil.hasPermission("user-add"); StpUtil.hasPermission("user-update"); StpUtil.hasPermission("art-add");
StpUtil.hasPermission("user-add"); StpUtil.hasPermission("user-delete"); StpUtil.hasPermission("art-delete");
StpUtil.hasPermission("index.js"); StpUtil.hasPermission("index.css"); StpUtil.hasPermission("index.html");
|
上帝权限:当一个账号拥有 "*"
权限时,他可以验证通过任何权限码 (角色认证同理)
3.7 如何把权限精确到按钮级
权限精确到按钮级的意思就是指:权限范围可以控制到页面上的每一个按钮是否显示。
- 在登录时,把当前账号拥有的所有权限码一次性返回给前端。
- 前端将权限码集合保存在
localStorage
或其它全局状态管理对象中。
- 在需要权限控制的按钮上,使用 js 进行逻辑判断
4 踢人下线
找到指定 loginId
对应的 Token
,并设置其失效。
4.1 强制注销
1 2 3
| StpUtil.logout(10001); StpUtil.logout(10001, "PC"); StpUtil.logoutByTokenValue("token");
|
4.2 踢人下线
1 2 3
| StpUtil.kickout(10001); StpUtil.kickout(10001, "PC"); StpUtil.kickoutByTokenValue("token");
|
强制注销 和 踢人下线 的区别在于:
- 强制注销等价于对方主动调用了注销方法,再次访问会提示:Token无效。
- 踢人下线不会清除Token信息,而是将其打上特定标记,再次访问会提示:Token已被踢下线。
4.3 账号封禁
1 2 3 4 5 6 7 8 9 10 11 12 13
|
StpUtil.disable(10001, 86400);
StpUtil.isDisable(10001);
StpUtil.getDisableTime(10001);
StpUtil.untieDisable(10001);
|
注意
对于正在登录的账号,对其账号封禁时并不会使其立刻注销
如果需要将其封禁后立即掉线,可采取先踢再封禁的策略。
5 注解鉴权
6 路由拦截鉴权
7 Session会话
8 框架配置