diff --git a/README.md b/README.md index fb64fd58..be65b772 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

logo

-

Sa-Token v1.30.0.RC

+

Sa-Token v1.30.0

一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!

@@ -28,64 +28,6 @@ **Sa-Token** 是一个轻量级 Java 权限认证框架,主要解决:**`登录认证`**、**`权限认证`**、**`Session会话`**、**`单点登录`**、**`OAuth2.0`**、**`微服务网关鉴权`** 等一系列权限相关问题。 -Sa-Token 的 API 设计非常简单,有多简单呢?以登录认证为例,你只需要: - -``` java -// 在登录时写入当前会话的账号id -StpUtil.login(10001); - -// 然后在需要校验登录处调用以下方法: -// 如果当前会话未登录,这句代码会抛出 `NotLoginException` 异常 -StpUtil.checkLogin(); -``` - -至此,我们已经借助 Sa-Token 完成登录认证! - -此时的你小脑袋可能飘满了问号,就这么简单?自定义 Realm 呢?全局过滤器呢?我不用写各种配置文件吗? - -没错,在 Sa-Token 中,登录认证就是如此简单,不需要任何的复杂前置工作,只需这一行简单的API调用,就可以完成会话登录认证! - -当你受够 Shiro、SpringSecurity 等框架的三拜九叩之后,你就会明白,相对于这些传统老牌框架,Sa-Token 的 API 设计是多么的简单、优雅! - -权限认证示例(只有具备 `user:add` 权限的会话才可以进入请求) -``` java -@SaCheckPermission("user:add") -@RequestMapping("/user/insert") -public String insert(SysUser user) { - // ... - return "用户增加"; -} -``` - -将某个账号踢下线(待到对方再次访问系统时会抛出`NotLoginException`异常) -``` java -// 将账号id为 10001 的会话踢下线 -StpUtil.kickout(10001); -``` - -在 Sa-Token 中,绝大多数功能都可以 **一行代码** 完成: -``` java -StpUtil.login(10001); // 标记当前会话登录的账号id -StpUtil.getLoginId(); // 获取当前会话登录的账号id -StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false -StpUtil.logout(); // 当前会话注销登录 -StpUtil.kickout(10001); // 将账号为10001的会话踢下线 -StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false -StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false -StpUtil.getSession(); // 获取当前账号id的Session -StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session -StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值 -StpUtil.login(10001, "PC"); // 指定设备类型登录,常用于“同端互斥登录” -StpUtil.kickout(10001, "PC"); // 指定账号指定设备类型踢下线 (不同端不受影响) -StpUtil.openSafe(120); // 在当前会话开启二级认证,有效期为120秒 -StpUtil.checkSafe(); // 校验当前会话是否处于二级认证有效期内,校验失败会抛出异常 -StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号 -``` - -即使不运行测试,相信您也能意会到绝大多数 API 的用法。 - - -## Sa-Token 功能一览 - **登录认证** —— 单端登录、多端登录、同端互斥登录、七天内免登录 - **权限认证** —— 权限认证、角色认证、会话二级认证 @@ -119,7 +61,7 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号 ## Sa-Token-SSO 单点登录 -网上的单点登录教程大多以CAS流程为主,其实对于不同的系统架构,实现单点登录的步骤也大为不同,Sa-Token由简入难将其划分为三种模式: +Sa-Token-SSO 由简入难划分为三种模式,解决不同架构下的 SSO 接入问题: | 系统架构 | 采用模式 | 简介 | 文档链接 | | :-------- | :-------- | :-------- | :-------- | @@ -131,8 +73,6 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号 1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com`、`c2.domain.com`、`c3.domain.com` 2. 后端同Redis:就是指多个系统可以连接同一个Redis。PS:这里并不需要把所有项目的数据都放在同一个Redis中,Sa-Token提供了 **`[权限缓存与业务缓存分离]`** 的解决方案,详情戳:[Alone独立Redis插件](http://sa-token.dev33.cn/doc/index.html#/plugin/alone-redis) 3. 如果既无法做到前端同域,也无法做到后端同Redis,那么只能走模式三,Http请求获取会话(Sa-Token对SSO提供了完整的封装,你只需要按照示例从文档上复制几段代码便可以轻松集成) -4. 技术选型一定要根据系统架构对症下药,切不可胡乱选择 - ## Sa-Token-OAuth2.0 授权登录 Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749) 编写,通过Sa-OAuth2你可以非常轻松的实现系统的OAuth2.0授权认证 @@ -154,12 +94,6 @@ Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749) ![sa-token-rz](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/x/sa-token-rz2.png 's-w') -## Star 趋势 -[![giteye-chart](https://chart.giteye.net/gitee/dromara/sa-token/77YQZ6UK.png 'Gitee')](https://giteye.net/chart/77YQZ6UK) - -[![github-chart](https://starchart.cc/dromara/sa-token.svg 'GitHub')](https://starchart.cc/dromara/sa-token) - - ## 使用Sa-Token的开源项目 - [[ sa-plus ]](https://gitee.com/click33/sa-plus):一个基于 SpringBoot 架构的快速开发框架,内置代码生成器 @@ -201,11 +135,6 @@ Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749) - [[ TLog ]](https://gitee.com/dromara/TLog):一个轻量级的分布式日志标记追踪神器 -## 贡献者名单 -感谢每一个为 Sa-Token 贡献代码的小伙伴 - -[![Giteye chart](https://chart.giteye.net/gitee/dromara/sa-token/CGZ7GT8E.png)](https://giteye.net/chart/CGZ7GT8E) - ## 交流群 QQ交流群:1群:1002350610 (已满) 、 diff --git a/pom.xml b/pom.xml index 73074ff6..d982dc28 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ - 1.30.0.RC + 1.30.0 1.8 utf-8 utf-8 diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenAction.java b/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenAction.java deleted file mode 100644 index 31a4833e..00000000 --- a/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenAction.java +++ /dev/null @@ -1,54 +0,0 @@ -//package cn.dev33.satoken.action; -// -//import java.lang.reflect.AnnotatedElement; -//import java.lang.reflect.Method; -//import java.util.List; -// -//import cn.dev33.satoken.session.SaSession; -// -///** -// *

v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy

-// *

Sa-Token 逻辑代理接口

-// *

此接口将会代理框架内部的一些关键性逻辑,方便开发者进行按需重写

-// * @author kong -// * -// */ -//@Deprecated -//public interface SaTokenAction { -// -// /** -// * 创建一个Token -// * @param loginId 账号id -// * @param loginType 账号类型 -// * @return token -// */ -// public String createToken(Object loginId, String loginType); -// -// /** -// * 创建一个Session -// * @param sessionId Session的Id -// * @return 创建后的Session -// */ -// public SaSession createSession(String sessionId); -// -// /** -// * 判断:集合中是否包含指定元素(模糊匹配) -// * @param list 集合 -// * @param element 元素 -// * @return 是否包含 -// */ -// public boolean hasElement(List list, String element); -// -// /** -// * 对一个Method对象进行注解检查(注解鉴权内部实现) -// * @param method Method对象 -// */ -// public void checkMethodAnnotation(Method method); -// -// /** -// * 从指定元素校验注解 -// * @param target / -// */ -// public void validateAnnotation(AnnotatedElement target); -// -//} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenActionDefaultImpl.java b/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenActionDefaultImpl.java deleted file mode 100644 index 8aa217c9..00000000 --- a/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenActionDefaultImpl.java +++ /dev/null @@ -1,150 +0,0 @@ -//package cn.dev33.satoken.action; -// -//import java.lang.reflect.AnnotatedElement; -//import java.lang.reflect.Method; -//import java.util.List; -//import java.util.UUID; -// -//import cn.dev33.satoken.SaManager; -//import cn.dev33.satoken.annotation.SaCheckBasic; -//import cn.dev33.satoken.annotation.SaCheckLogin; -//import cn.dev33.satoken.annotation.SaCheckPermission; -//import cn.dev33.satoken.annotation.SaCheckRole; -//import cn.dev33.satoken.annotation.SaCheckSafe; -//import cn.dev33.satoken.basic.SaBasicUtil; -//import cn.dev33.satoken.session.SaSession; -//import cn.dev33.satoken.strategy.SaStrategy; -//import cn.dev33.satoken.util.SaFoxUtil; -//import cn.dev33.satoken.util.SaTokenConsts; -// -///** -// *

v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy

-// *

Sa-Token 逻辑代理接口 [默认实现类]

-// * @author kong -// * -// */ -//@Deprecated -//public class SaTokenActionDefaultImpl implements SaTokenAction { -// -// /** -// * 创建一个Token -// */ -// @Override -// public String createToken(Object loginId, String loginType) { -// // 根据配置的tokenStyle生成不同风格的token -// String tokenStyle = SaManager.getConfig().getTokenStyle(); -// // uuid -// if(SaTokenConsts.TOKEN_STYLE_UUID.equals(tokenStyle)) { -// return UUID.randomUUID().toString(); -// } -// // 简单uuid (不带下划线) -// if(SaTokenConsts.TOKEN_STYLE_SIMPLE_UUID.equals(tokenStyle)) { -// return UUID.randomUUID().toString().replaceAll("-", ""); -// } -// // 32位随机字符串 -// if(SaTokenConsts.TOKEN_STYLE_RANDOM_32.equals(tokenStyle)) { -// return SaFoxUtil.getRandomString(32); -// } -// // 64位随机字符串 -// if(SaTokenConsts.TOKEN_STYLE_RANDOM_64.equals(tokenStyle)) { -// return SaFoxUtil.getRandomString(64); -// } -// // 128位随机字符串 -// if(SaTokenConsts.TOKEN_STYLE_RANDOM_128.equals(tokenStyle)) { -// return SaFoxUtil.getRandomString(128); -// } -// // tik风格 (2_14_16) -// if(SaTokenConsts.TOKEN_STYLE_TIK.equals(tokenStyle)) { -// return SaFoxUtil.getRandomString(2) + "_" + SaFoxUtil.getRandomString(14) + "_" + SaFoxUtil.getRandomString(16) + "__"; -// } -// // 默认,还是uuid -// return UUID.randomUUID().toString(); -// } -// -// /** -// * 创建一个Session -// */ -// @Override -// public SaSession createSession(String sessionId) { -// return new SaSession(sessionId); -// } -// -// /** -// * 判断:集合中是否包含指定元素(模糊匹配) -// */ -// @Override -// public boolean hasElement(List list, String element) { -// -// // 空集合直接返回false -// if(list == null || list.size() == 0) { -// return false; -// } -// -// // 先尝试一下简单匹配,如果可以匹配成功则无需继续模糊匹配 -// if (list.contains(element)) { -// return true; -// } -// -// // 开始模糊匹配 -// for (String patt : list) { -// if(SaFoxUtil.vagueMatch(patt, element)) { -// return true; -// } -// } -// -// // 走出for循环说明没有一个元素可以匹配成功 -// return false; -// } -// -// /** -// * 对一个Method对象进行注解检查(注解鉴权内部实现) -// */ -// @Override -// public void checkMethodAnnotation(Method method) { -// -// // 先校验 Method 所属 Class 上的注解 -// validateAnnotation(method.getDeclaringClass()); -// -// // 再校验 Method 上的注解 -// validateAnnotation(method); -// } -// -// /** -// * 从指定元素校验注解 -// * @param target see note -// */ -// public void validateAnnotation(AnnotatedElement target) { -// -// // 校验 @SaCheckLogin 注解 -// SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.me.getAnnotation.apply(target, SaCheckLogin.class); -// if(checkLogin != null) { -// SaManager.getStpLogic(checkLogin.type()).checkByAnnotation(checkLogin); -// } -// -// // 校验 @SaCheckRole 注解 -// SaCheckRole checkRole = (SaCheckRole) SaStrategy.me.getAnnotation.apply(target, SaCheckRole.class); -// if(checkRole != null) { -// SaManager.getStpLogic(checkRole.type()).checkByAnnotation(checkRole); -// } -// -// // 校验 @SaCheckPermission 注解 -// SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.me.getAnnotation.apply(target, SaCheckPermission.class); -// if(checkPermission != null) { -// SaManager.getStpLogic(checkPermission.type()).checkByAnnotation(checkPermission); -// } -// -// // 校验 @SaCheckSafe 注解 -// SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.me.getAnnotation.apply(target, SaCheckSafe.class); -// if(checkSafe != null) { -// SaManager.getStpLogic(checkSafe.type()).checkByAnnotation(checkSafe); -// } -// -// // 校验 @SaCheckBasic 注解 -// SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.me.getAnnotation.apply(target, SaCheckBasic.class); -// if(checkBasic != null) { -// SaBasicUtil.check(checkBasic.realm(), checkBasic.account()); -// } -// -// } -// -//} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaExceptionCode.java b/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaExceptionCode.java index a1a67078..88005237 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaExceptionCode.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaExceptionCode.java @@ -8,7 +8,7 @@ package cn.dev33.satoken.exception; */ public class SaExceptionCode { - /** 代表未指定异常细分状态码 */ + /** 代表这个异常在抛出时未指定异常细分状态码 */ public static final int CODE_UNDEFINED = -1; } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java index c9d6d946..7eace922 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java @@ -13,7 +13,7 @@ public class SaTokenConsts { /** * Sa-Token 当前版本号 */ - public static final String VERSION_NO = "v1.30.0.RC"; + public static final String VERSION_NO = "v1.30.0"; /** * Sa-Token 开源地址 Gitee diff --git a/sa-token-demo/sa-token-demo-alone-redis/pom.xml b/sa-token-demo/sa-token-demo-alone-redis/pom.xml index aca5ab64..6886e9d2 100644 --- a/sa-token-demo/sa-token-demo-alone-redis/pom.xml +++ b/sa-token-demo/sa-token-demo-alone-redis/pom.xml @@ -16,7 +16,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-dubbo-consumer/pom.xml b/sa-token-demo/sa-token-demo-dubbo-consumer/pom.xml index ebba72f6..8a941bc5 100644 --- a/sa-token-demo/sa-token-demo-dubbo-consumer/pom.xml +++ b/sa-token-demo/sa-token-demo-dubbo-consumer/pom.xml @@ -16,7 +16,7 @@ 1.8 3.1.1 - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-dubbo-provider/pom.xml b/sa-token-demo/sa-token-demo-dubbo-provider/pom.xml index 9ba7e59d..c9292252 100644 --- a/sa-token-demo/sa-token-demo-dubbo-provider/pom.xml +++ b/sa-token-demo/sa-token-demo-dubbo-provider/pom.xml @@ -16,7 +16,7 @@ 1.8 3.1.1 - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-jwt/pom.xml b/sa-token-demo/sa-token-demo-jwt/pom.xml index 489e645d..6e348ac0 100644 --- a/sa-token-demo/sa-token-demo-jwt/pom.xml +++ b/sa-token-demo/sa-token-demo-jwt/pom.xml @@ -16,7 +16,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-oauth2-client/pom.xml b/sa-token-demo/sa-token-demo-oauth2-client/pom.xml index f3280320..2617edcc 100644 --- a/sa-token-demo/sa-token-demo-oauth2-client/pom.xml +++ b/sa-token-demo/sa-token-demo-oauth2-client/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-oauth2-server/pom.xml b/sa-token-demo/sa-token-demo-oauth2-server/pom.xml index ec4488a8..ff5ce390 100644 --- a/sa-token-demo/sa-token-demo-oauth2-server/pom.xml +++ b/sa-token-demo/sa-token-demo-oauth2-server/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-quick-login/pom.xml b/sa-token-demo/sa-token-demo-quick-login/pom.xml index 9f951b93..cf0f7218 100644 --- a/sa-token-demo/sa-token-demo-quick-login/pom.xml +++ b/sa-token-demo/sa-token-demo-quick-login/pom.xml @@ -16,7 +16,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-solon/pom.xml b/sa-token-demo/sa-token-demo-solon/pom.xml index 09ff61eb..53b55bbe 100644 --- a/sa-token-demo/sa-token-demo-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-solon/pom.xml @@ -9,7 +9,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-springboot-redis/.gitignore b/sa-token-demo/sa-token-demo-springboot-redis/.gitignore new file mode 100644 index 00000000..99a6e767 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/.gitignore @@ -0,0 +1,12 @@ +target/ + +node_modules/ +bin/ +.settings/ +unpackage/ +.classpath +.project + +.idea/ + +.factorypath \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-springboot-redis/pom.xml b/sa-token-demo/sa-token-demo-springboot-redis/pom.xml new file mode 100644 index 00000000..0dc8aa5e --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/pom.xml @@ -0,0 +1,72 @@ + + 4.0.0 + cn.dev33 + sa-token-demo-springboot-redis + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-parent + 2.5.12 + + + + + + + 1.30.0 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-aop + + + + + cn.dev33 + sa-token-spring-boot-starter + ${sa-token-version} + + + + + + + + cn.dev33 + sa-token-dao-redis-jackson + ${sa-token-version} + + + + + org.apache.commons + commons-pool2 + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java new file mode 100644 index 00000000..4f5e4467 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java @@ -0,0 +1,21 @@ +package com.pj; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import cn.dev33.satoken.SaManager; + +/** + * Sa-Token整合SpringBoot 示例,整合redis + * @author kong + * + */ +@SpringBootApplication +public class SaTokenDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(SaTokenDemoApplication.class, args); + System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig()); + } + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/GlobalException.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/GlobalException.java new file mode 100644 index 00000000..26b7cd5d --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/GlobalException.java @@ -0,0 +1,57 @@ +package com.pj.current; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import com.pj.util.AjaxJson; + +import cn.dev33.satoken.exception.DisableLoginException; +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotPermissionException; +import cn.dev33.satoken.exception.NotRoleException; + +/** + * 全局异常处理 + */ +@RestControllerAdvice +public class GlobalException { + + // 全局异常拦截(拦截项目中的所有异常) + @ExceptionHandler + public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) + throws Exception { + + // 打印堆栈,以供调试 + System.out.println("全局异常---------------"); + e.printStackTrace(); + + // 不同异常返回不同状态码 + AjaxJson aj = null; + if (e instanceof NotLoginException) { // 如果是未登录异常 + NotLoginException ee = (NotLoginException) e; + aj = AjaxJson.getNotLogin().setMsg(ee.getMessage()); + } + else if(e instanceof NotRoleException) { // 如果是角色异常 + NotRoleException ee = (NotRoleException) e; + aj = AjaxJson.getNotJur("无此角色:" + ee.getRole()); + } + else if(e instanceof NotPermissionException) { // 如果是权限异常 + NotPermissionException ee = (NotPermissionException) e; + aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission()); + } + else if(e instanceof DisableLoginException) { // 如果是被封禁异常 + DisableLoginException ee = (DisableLoginException) e; + aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封"); + } + else { // 普通异常, 输出:500 + 异常信息 + aj = AjaxJson.getError(e.getMessage()); + } + + // 返回给前端 + return aj; + } + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/NotFoundHandle.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/NotFoundHandle.java new file mode 100644 index 00000000..1e82e95a --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/NotFoundHandle.java @@ -0,0 +1,27 @@ +package com.pj.current; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import cn.dev33.satoken.util.SaResult; + +/** + * 处理 404 + * @author kong + */ +@RestController +public class NotFoundHandle implements ErrorController { + + @RequestMapping("/error") + public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setStatus(200); + return SaResult.get(404, "not found", null); + } + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java new file mode 100644 index 00000000..5f3212e7 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -0,0 +1,85 @@ +package com.pj.satoken; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.pj.util.AjaxJson; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.filter.SaServletFilter; +import cn.dev33.satoken.interceptor.SaAnnotationInterceptor; +import cn.dev33.satoken.strategy.SaStrategy; + + +/** + * [Sa-Token 权限认证] 配置类 + * @author kong + * + */ +@Configuration +public class SaTokenConfigure implements WebMvcConfigurer { + + /** + * 注册Sa-Token 的拦截器,打开注解式鉴权功能 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册注解拦截器 + registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**"); + } + + /** + * 注册 [Sa-Token 全局过滤器] + */ + @Bean + public SaServletFilter getSaServletFilter() { + return new SaServletFilter() + + // 指定 [拦截路由] 与 [放行路由] + .addInclude("/**")// .addExclude("/favicon.ico") + + // 认证函数: 每次请求执行 + .setAuth(obj -> { + // System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath()); + + }) + + // 异常处理函数:每次认证函数发生异常时执行此函数 + .setError(e -> { + System.out.println("---------- sa全局异常 "); + return AjaxJson.getError(e.getMessage()); + }) + + // 前置函数:在每次认证函数之前执行 + .setBeforeAuth(r -> { + // ---------- 设置一些安全响应头 ---------- + SaHolder.getResponse() + // 服务器名称 + .setServer("sa-server") + // 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以 + .setHeader("X-Frame-Options", "SAMEORIGIN") + // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面 + .setHeader("X-XSS-Protection", "1; mode=block") + // 禁用浏览器内容嗅探 + .setHeader("X-Content-Type-Options", "nosniff") + ; + }) + ; + } + + /** + * 重写 Sa-Token 框架内部算法策略 + */ + @Autowired + public void rewriteSaStrategy() { + // 重写Sa-Token的注解处理器,增加注解合并功能 + SaStrategy.me.getAnnotation = (element, annotationClass) -> { + return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass); + }; + } + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/StpInterfaceImpl.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/StpInterfaceImpl.java new file mode 100644 index 00000000..b6cc79f7 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/StpInterfaceImpl.java @@ -0,0 +1,44 @@ +package com.pj.satoken; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Component; + +import cn.dev33.satoken.stp.StpInterface; + +/** + * 自定义权限验证接口扩展 + */ +@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展 +public class StpInterfaceImpl implements StpInterface { + + /** + * 返回一个账号所拥有的权限码集合 + */ + @Override + public List getPermissionList(Object loginId, String loginType) { + // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限 + List list = new ArrayList(); + 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 getRoleList(Object loginId, String loginType) { + // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色 + List list = new ArrayList(); + list.add("admin"); + list.add("super-admin"); + return list; + } + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckLogin.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckLogin.java new file mode 100644 index 00000000..74014898 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckLogin.java @@ -0,0 +1,21 @@ +package com.pj.satoken.at; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import cn.dev33.satoken.annotation.SaCheckLogin; + +/** + * 登录认证(User版):只有登录之后才能进入该方法 + *

可标注在函数、类上(效果等同于标注在此类的所有方法上) + * @author kong + * + */ +@SaCheckLogin(type = StpUserUtil.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE}) +public @interface SaUserCheckLogin { + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckPermission.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckPermission.java new file mode 100644 index 00000000..38312116 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckPermission.java @@ -0,0 +1,38 @@ +package com.pj.satoken.at; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; + +/** + * 权限认证(User版):必须具有指定权限才能进入该方法 + *

可标注在函数、类上(效果等同于标注在此类的所有方法上) + * @author kong + * + */ +@SaCheckPermission(type = StpUserUtil.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE}) +public @interface SaUserCheckPermission { + + /** + * 需要校验的权限码 + * @return 需要校验的权限码 + */ + @AliasFor(annotation = SaCheckPermission.class) + String [] value() default {}; + + /** + * 验证模式:AND | OR,默认AND + * @return 验证模式 + */ + @AliasFor(annotation = SaCheckPermission.class) + SaMode mode() default SaMode.AND; + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckRole.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckRole.java new file mode 100644 index 00000000..c9b44f32 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckRole.java @@ -0,0 +1,38 @@ +package com.pj.satoken.at; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +import cn.dev33.satoken.annotation.SaCheckRole; +import cn.dev33.satoken.annotation.SaMode; + +/** + * 角色认证(User版):必须具有指定角色标识才能进入该方法 + *

可标注在函数、类上(效果等同于标注在此类的所有方法上) + * @author kong + * + */ +@SaCheckRole(type = StpUserUtil.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE}) +public @interface SaUserCheckRole { + + /** + * 需要校验的角色标识 + * @return 需要校验的角色标识 + */ + @AliasFor(annotation = SaCheckRole.class) + String [] value() default {}; + + /** + * 验证模式:AND | OR,默认AND + * @return 验证模式 + */ + @AliasFor(annotation = SaCheckRole.class) + SaMode mode() default SaMode.AND; + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/StpUserUtil.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/StpUserUtil.java new file mode 100644 index 00000000..939b5fe8 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/StpUserUtil.java @@ -0,0 +1,912 @@ +package com.pj.satoken.at; + +import java.util.List; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.fun.SaFunction; +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.SaTokenInfo; +import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.stp.StpUtil; + +/** + * Sa-Token 权限认证工具类 (user版) + * @author kong + */ +public class StpUserUtil { + + /** + * 账号类型标识 + */ + public static final String TYPE = "user"; + + /** + * 底层的 StpLogic 对象 + */ + public static StpLogic stpLogic = new StpLogic(TYPE); + + /** + * 获取当前 StpLogic 的账号类型 + * @return See Note + */ + public static String getLoginType(){ + return stpLogic.getLoginType(); + } + + /** + * 重置 StpLogic 对象 + * @param stpLogic / + */ + public static void setStpLogic(StpLogic stpLogic) { + StpUtil.stpLogic = stpLogic; + // 防止自定义 stpLogic 被覆盖 + SaManager.putStpLogic(stpLogic); + } + + + // =================== 获取token 相关 =================== + + /** + * 返回token名称 + * @return 此StpLogic的token名称 + */ + public static String getTokenName() { + return stpLogic.getTokenName(); + } + + /** + * 在当前会话写入当前TokenValue + * @param tokenValue token值 + */ + public static void setTokenValue(String tokenValue){ + stpLogic.setTokenValue(tokenValue); + } + + /** + * 在当前会话写入当前TokenValue + * @param tokenValue token值 + * @param cookieTimeout Cookie存活时间(秒) + */ + public static void setTokenValue(String tokenValue, int cookieTimeout){ + stpLogic.setTokenValue(tokenValue, cookieTimeout); + } + + /** + * 获取当前TokenValue + * @return 当前tokenValue + */ + public static String getTokenValue() { + return stpLogic.getTokenValue(); + } + + /** + * 获取当前TokenValue (不裁剪前缀) + * @return / + */ + public static String getTokenValueNotCut(){ + return stpLogic.getTokenValueNotCut(); + } + + /** + * 获取当前会话的Token信息 + * @return token信息 + */ + public static SaTokenInfo getTokenInfo() { + return stpLogic.getTokenInfo(); + } + + + // =================== 登录相关操作 =================== + + // --- 登录 + + /** + * 会话登录 + * @param id 账号id,建议的类型:(long | int | String) + */ + public static void login(Object id) { + stpLogic.login(id); + } + + /** + * 会话登录,并指定登录设备类型 + * @param id 账号id,建议的类型:(long | int | String) + * @param device 设备类型 + */ + public static void login(Object id, String device) { + stpLogic.login(id, device); + } + + /** + * 会话登录,并指定是否 [记住我] + * @param id 账号id,建议的类型:(long | int | String) + * @param isLastingCookie 是否为持久Cookie + */ + public static void login(Object id, boolean isLastingCookie) { + stpLogic.login(id, isLastingCookie); + } + + /** + * 会话登录,并指定所有登录参数Model + * @param id 登录id,建议的类型:(long | int | String) + * @param loginModel 此次登录的参数Model + */ + public static void login(Object id, SaLoginModel loginModel) { + stpLogic.login(id, loginModel); + } + + /** + * 创建指定账号id的登录会话 + * @param id 登录id,建议的类型:(long | int | String) + * @return 返回会话令牌 + */ + public static String createLoginSession(Object id) { + return stpLogic.createLoginSession(id); + } + + /** + * 创建指定账号id的登录会话 + * @param id 登录id,建议的类型:(long | int | String) + * @param loginModel 此次登录的参数Model + * @return 返回会话令牌 + */ + public static String createLoginSession(Object id, SaLoginModel loginModel) { + return stpLogic.createLoginSession(id, loginModel); + } + + // --- 注销 + + /** + * 会话注销 + */ + public static void logout() { + stpLogic.logout(); + } + + /** + * 会话注销,根据账号id + * @param loginId 账号id + */ + public static void logout(Object loginId) { + stpLogic.logout(loginId); + } + + /** + * 会话注销,根据账号id 和 设备类型 + * + * @param loginId 账号id + * @param device 设备类型 (填null代表注销所有设备类型) + */ + public static void logout(Object loginId, String device) { + stpLogic.logout(loginId, device); + } + + /** + * 会话注销,根据指定 Token + * + * @param tokenValue 指定token + */ + public static void logoutByTokenValue(String tokenValue) { + stpLogic.logoutByTokenValue(tokenValue); + } + + /** + * 踢人下线,根据账号id + *

当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5

+ * + * @param loginId 账号id + */ + public static void kickout(Object loginId) { + stpLogic.kickout(loginId); + } + + /** + * 踢人下线,根据账号id 和 设备类型 + *

当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5

+ * + * @param loginId 账号id + * @param device 设备类型 (填null代表踢出所有设备类型) + */ + public static void kickout(Object loginId, String device) { + stpLogic.kickout(loginId, device); + } + + /** + * 踢人下线,根据指定 Token + *

当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5

+ * + * @param tokenValue 指定token + */ + public static void kickoutByTokenValue(String tokenValue) { + stpLogic.kickoutByTokenValue(tokenValue); + } + + /** + * 顶人下线,根据账号id 和 设备类型 + *

当对方再次访问系统时,会抛出NotLoginException异常,场景值=-4

+ * + * @param loginId 账号id + * @param device 设备类型 (填null代表顶替所有设备类型) + */ + public static void replaced(Object loginId, String device) { + stpLogic.replaced(loginId, device); + } + + + // 查询相关 + + /** + * 当前会话是否已经登录 + * @return 是否已登录 + */ + public static boolean isLogin() { + return stpLogic.isLogin(); + } + + /** + * 检验当前会话是否已经登录,如未登录,则抛出异常 + */ + public static void checkLogin() { + stpLogic.checkLogin(); + } + + /** + * 获取当前会话账号id, 如果未登录,则抛出异常 + * @return 账号id + */ + public static Object getLoginId() { + return stpLogic.getLoginId(); + } + + /** + * 获取当前会话账号id, 如果未登录,则返回默认值 + * @param 返回类型 + * @param defaultValue 默认值 + * @return 登录id + */ + public static T getLoginId(T defaultValue) { + return stpLogic.getLoginId(defaultValue); + } + + /** + * 获取当前会话账号id, 如果未登录,则返回null + * @return 账号id + */ + public static Object getLoginIdDefaultNull() { + return stpLogic.getLoginIdDefaultNull(); + } + + /** + * 获取当前会话账号id, 并转换为String类型 + * @return 账号id + */ + public static String getLoginIdAsString() { + return stpLogic.getLoginIdAsString(); + } + + /** + * 获取当前会话账号id, 并转换为int类型 + * @return 账号id + */ + public static int getLoginIdAsInt() { + return stpLogic.getLoginIdAsInt(); + } + + /** + * 获取当前会话账号id, 并转换为long类型 + * @return 账号id + */ + public static long getLoginIdAsLong() { + return stpLogic.getLoginIdAsLong(); + } + + /** + * 获取指定Token对应的账号id,如果未登录,则返回 null + * @param tokenValue token + * @return 账号id + */ + public static Object getLoginIdByToken(String tokenValue) { + return stpLogic.getLoginIdByToken(tokenValue); + } + + /** + * 获取Token扩展信息(只在jwt模式下有效) + * @param key 键值 + * @return 对应的扩展数据 + */ + public static Object getExtra(String key) { + return stpLogic.getExtra(key); + } + + + // =================== User-Session 相关 =================== + + /** + * 获取指定账号id的Session, 如果Session尚未创建,isCreate=是否新建并返回 + * @param loginId 账号id + * @param isCreate 是否新建 + * @return Session对象 + */ + public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) { + return stpLogic.getSessionByLoginId(loginId, isCreate); + } + + /** + * 获取指定key的Session, 如果Session尚未创建,则返回null + * @param sessionId SessionId + * @return Session对象 + */ + public static SaSession getSessionBySessionId(String sessionId) { + return stpLogic.getSessionBySessionId(sessionId); + } + + /** + * 获取指定账号id的Session,如果Session尚未创建,则新建并返回 + * @param loginId 账号id + * @return Session对象 + */ + public static SaSession getSessionByLoginId(Object loginId) { + return stpLogic.getSessionByLoginId(loginId); + } + + /** + * 获取当前会话的Session, 如果Session尚未创建,isCreate=是否新建并返回 + * @param isCreate 是否新建 + * @return Session对象 + */ + public static SaSession getSession(boolean isCreate) { + return stpLogic.getSession(isCreate); + } + + /** + * 获取当前会话的Session,如果Session尚未创建,则新建并返回 + * @return Session对象 + */ + public static SaSession getSession() { + return stpLogic.getSession(); + } + + + // =================== Token-Session 相关 =================== + + /** + * 获取指定Token-Session,如果Session尚未创建,则新建并返回 + * @param tokenValue Token值 + * @return Session对象 + */ + public static SaSession getTokenSessionByToken(String tokenValue) { + return stpLogic.getTokenSessionByToken(tokenValue); + } + + /** + * 获取当前Token-Session,如果Session尚未创建,则新建并返回 + * @return Session对象 + */ + public static SaSession getTokenSession() { + return stpLogic.getTokenSession(); + } + + + // =================== [临时有效期] 验证相关 =================== + + /** + * 检查当前token 是否已经[临时过期],如果已经过期则抛出异常 + */ + public static void checkActivityTimeout() { + stpLogic.checkActivityTimeout(); + } + + /** + * 续签当前token:(将 [最后操作时间] 更新为当前时间戳) + *

请注意: 即使token已经 [临时过期] 也可续签成功, + * 如果此场景下需要提示续签失败,可在此之前调用 checkActivityTimeout() 强制检查是否过期即可

+ */ + public static void updateLastActivityToNow() { + stpLogic.updateLastActivityToNow(); + } + + + // =================== 过期时间相关 =================== + + /** + * 获取当前登录者的 token 剩余有效时间 (单位: 秒) + * @return token剩余有效时间 + */ + public static long getTokenTimeout() { + return stpLogic.getTokenTimeout(); + } + + /** + * 获取当前登录者的 User-Session 剩余有效时间 (单位: 秒) + * @return token剩余有效时间 + */ + public static long getSessionTimeout() { + return stpLogic.getSessionTimeout(); + } + + /** + * 获取当前 Token-Session 剩余有效时间 (单位: 秒) + * @return token剩余有效时间 + */ + public static long getTokenSessionTimeout() { + return stpLogic.getTokenSessionTimeout(); + } + + /** + * 获取当前 token [临时过期] 剩余有效时间 (单位: 秒) + * @return token [临时过期] 剩余有效时间 + */ + public static long getTokenActivityTimeout() { + return stpLogic.getTokenActivityTimeout(); + } + + /** + * 对当前 Token 的 timeout 值进行续期 + * @param timeout 要修改成为的有效时间 (单位: 秒) + */ + public static void renewTimeout(long timeout) { + stpLogic.renewTimeout(timeout); + } + + /** + * 对指定 Token 的 timeout 值进行续期 + * @param tokenValue 指定token + * @param timeout 要修改成为的有效时间 (单位: 秒) + */ + public static void renewTimeout(String tokenValue, long timeout) { + stpLogic.renewTimeout(tokenValue, timeout); + } + + // =================== 角色验证操作 =================== + + /** + * 获取:当前账号的角色集合 + * @return / + */ + public static List getRoleList() { + return stpLogic.getRoleList(); + } + + /** + * 获取:指定账号的角色集合 + * @param loginId 指定账号id + * @return / + */ + public static List getRoleList(Object loginId) { + return stpLogic.getRoleList(loginId); + } + + /** + * 判断:当前账号是否拥有指定角色, 返回true或false + * @param role 角色标识 + * @return 是否含有指定角色标识 + */ + public static boolean hasRole(String role) { + return stpLogic.hasRole(role); + } + + /** + * 判断:指定账号是否含有指定角色标识, 返回true或false + * @param loginId 账号id + * @param role 角色标识 + * @return 是否含有指定角色标识 + */ + public static boolean hasRole(Object loginId, String role) { + return stpLogic.hasRole(loginId, role); + } + + /** + * 判断:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过] + * @param roleArray 角色标识数组 + * @return true或false + */ + public static boolean hasRoleAnd(String... roleArray){ + return stpLogic.hasRoleAnd(roleArray); + } + + /** + * 判断:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可] + * @param roleArray 角色标识数组 + * @return true或false + */ + public static boolean hasRoleOr(String... roleArray){ + return stpLogic.hasRoleOr(roleArray); + } + + /** + * 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException + * @param role 角色标识 + */ + public static void checkRole(String role) { + stpLogic.checkRole(role); + } + + /** + * 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过] + * @param roleArray 角色标识数组 + */ + public static void checkRoleAnd(String... roleArray){ + stpLogic.checkRoleAnd(roleArray); + } + + /** + * 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可] + * @param roleArray 角色标识数组 + */ + public static void checkRoleOr(String... roleArray){ + stpLogic.checkRoleOr(roleArray); + } + + + // =================== 权限验证操作 =================== + + /** + * 获取:当前账号的权限码集合 + * @return / + */ + public static List getPermissionList() { + return stpLogic.getPermissionList(); + } + + /** + * 获取:指定账号的权限码集合 + * @param loginId 指定账号id + * @return / + */ + public static List getPermissionList(Object loginId) { + return stpLogic.getPermissionList(loginId); + } + + /** + * 判断:当前账号是否含有指定权限, 返回true或false + * @param permission 权限码 + * @return 是否含有指定权限 + */ + public static boolean hasPermission(String permission) { + return stpLogic.hasPermission(permission); + } + + /** + * 判断:指定账号id是否含有指定权限, 返回true或false + * @param loginId 账号id + * @param permission 权限码 + * @return 是否含有指定权限 + */ + public static boolean hasPermission(Object loginId, String permission) { + return stpLogic.hasPermission(loginId, permission); + } + + /** + * 判断:当前账号是否含有指定权限, [指定多个,必须全部具有] + * @param permissionArray 权限码数组 + * @return true 或 false + */ + public static boolean hasPermissionAnd(String... permissionArray){ + return stpLogic.hasPermissionAnd(permissionArray); + } + + /** + * 判断:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] + * @param permissionArray 权限码数组 + * @return true 或 false + */ + public static boolean hasPermissionOr(String... permissionArray){ + return stpLogic.hasPermissionOr(permissionArray); + } + + /** + * 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException + * @param permission 权限码 + */ + public static void checkPermission(String permission) { + stpLogic.checkPermission(permission); + } + + /** + * 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过] + * @param permissionArray 权限码数组 + */ + public static void checkPermissionAnd(String... permissionArray) { + stpLogic.checkPermissionAnd(permissionArray); + } + + /** + * 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] + * @param permissionArray 权限码数组 + */ + public static void checkPermissionOr(String... permissionArray) { + stpLogic.checkPermissionOr(permissionArray); + } + + + // =================== id 反查token 相关操作 =================== + + /** + * 获取指定账号id的tokenValue + *

在配置为允许并发登录时,此方法只会返回队列的最后一个token, + * 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId + * @param loginId 账号id + * @return token值 + */ + public static String getTokenValueByLoginId(Object loginId) { + return stpLogic.getTokenValueByLoginId(loginId); + } + + /** + * 获取指定账号id指定设备类型端的tokenValue + *

在配置为允许并发登录时,此方法只会返回队列的最后一个token, + * 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId + * @param loginId 账号id + * @param device 设备类型 + * @return token值 + */ + public static String getTokenValueByLoginId(Object loginId, String device) { + return stpLogic.getTokenValueByLoginId(loginId, device); + } + + /** + * 获取指定账号id的tokenValue集合 + * @param loginId 账号id + * @return 此loginId的所有相关token + */ + public static List getTokenValueListByLoginId(Object loginId) { + return stpLogic.getTokenValueListByLoginId(loginId); + } + + /** + * 获取指定账号id指定设备类型端的tokenValue 集合 + * @param loginId 账号id + * @param device 设备类型 + * @return 此loginId的所有相关token + */ + public static List getTokenValueListByLoginId(Object loginId, String device) { + return stpLogic.getTokenValueListByLoginId(loginId, device); + } + + /** + * 返回当前会话的登录设备类型 + * @return 当前令牌的登录设备类型 + */ + public static String getLoginDevice() { + return stpLogic.getLoginDevice(); + } + + + // =================== 会话管理 =================== + + /** + * 根据条件查询Token + * @param keyword 关键字 + * @param start 开始处索引 (-1代表查询所有) + * @param size 获取数量 + * @return token集合 + */ + public static List searchTokenValue(String keyword, int start, int size) { + return stpLogic.searchTokenValue(keyword, start, size); + } + + /** + * 根据条件查询SessionId + * @param keyword 关键字 + * @param start 开始处索引 (-1代表查询所有) + * @param size 获取数量 + * @return sessionId集合 + */ + public static List searchSessionId(String keyword, int start, int size) { + return stpLogic.searchSessionId(keyword, start, size); + } + + /** + * 根据条件查询Token专属Session的Id + * @param keyword 关键字 + * @param start 开始处索引 (-1代表查询所有) + * @param size 获取数量 + * @return sessionId集合 + */ + public static List searchTokenSessionId(String keyword, int start, int size) { + return stpLogic.searchTokenSessionId(keyword, start, size); + } + + + // ------------------- 账号封禁 ------------------- + + /** + * 封禁指定账号 + *

此方法不会直接将此账号id踢下线,而是在对方再次登录时抛出`DisableLoginException`异常 + * @param loginId 指定账号id + * @param disableTime 封禁时间, 单位: 秒 (-1=永久封禁) + */ + public static void disable(Object loginId, long disableTime) { + stpLogic.disable(loginId, disableTime); + } + + /** + * 指定账号是否已被封禁 (true=已被封禁, false=未被封禁) + * @param loginId 账号id + * @return see note + */ + public static boolean isDisable(Object loginId) { + return stpLogic.isDisable(loginId); + } + + /** + * 获取指定账号剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁) + * @param loginId 账号id + * @return see note + */ + public static long getDisableTime(Object loginId) { + return stpLogic.getDisableTime(loginId); + } + + /** + * 解封指定账号 + * @param loginId 账号id + */ + public static void untieDisable(Object loginId) { + stpLogic.untieDisable(loginId); + } + + + // =================== 身份切换 =================== + + /** + * 临时切换身份为指定账号id + * @param loginId 指定loginId + */ + public static void switchTo(Object loginId) { + stpLogic.switchTo(loginId); + } + + /** + * 结束临时切换身份 + */ + public static void endSwitch() { + stpLogic.endSwitch(); + } + + /** + * 当前是否正处于[身份临时切换]中 + * @return 是否正处于[身份临时切换]中 + */ + public static boolean isSwitch() { + return stpLogic.isSwitch(); + } + + /** + * 在一个代码段里方法内,临时切换身份为指定账号id + * @param loginId 指定账号id + * @param function 要执行的方法 + */ + public static void switchTo(Object loginId, SaFunction function) { + stpLogic.switchTo(loginId, function); + } + + + // ------------------- 二级认证 ------------------- + + /** + * 在当前会话 开启二级认证 + * @param safeTime 维持时间 (单位: 秒) + */ + public static void openSafe(long safeTime) { + stpLogic.openSafe(safeTime); + } + + /** + * 当前会话 是否处于二级认证时间内 + * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时 + */ + public static boolean isSafe() { + return stpLogic.isSafe(); + } + + /** + * 检查当前会话是否已通过二级认证,如未通过则抛出异常 + */ + public static void checkSafe() { + stpLogic.checkSafe(); + } + + /** + * 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证) + * @return 剩余有效时间 + */ + public static long getSafeTime() { + return stpLogic.getSafeTime(); + } + + /** + * 在当前会话 结束二级认证 + */ + public static void closeSafe() { + stpLogic.closeSafe(); + } + + + // =================== 历史API,兼容旧版本 =================== + + /** + *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.getLoginType() ,使用方式保持不变

+ * + * 获取当前StpLogin的loginKey + * @return 当前StpLogin的loginKey + */ + @Deprecated + public static String getLoginKey(){ + return stpLogic.getLoginType(); + } + + /** + *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变

+ * + * 在当前会话上登录id + * @param loginId 登录id,建议的类型:(long | int | String) + */ + @Deprecated + public static void setLoginId(Object loginId) { + stpLogic.login(loginId); + } + + /** + *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变

+ * + * 在当前会话上登录id, 并指定登录设备类型 + * @param loginId 登录id,建议的类型:(long | int | String) + * @param device 设备类型 + */ + @Deprecated + public static void setLoginId(Object loginId, String device) { + stpLogic.login(loginId, device); + } + + /** + *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变

+ * + * 在当前会话上登录id, 并指定登录设备类型 + * @param loginId 登录id,建议的类型:(long | int | String) + * @param isLastingCookie 是否为持久Cookie + */ + @Deprecated + public static void setLoginId(Object loginId, boolean isLastingCookie) { + stpLogic.login(loginId, isLastingCookie); + } + + /** + *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变

+ * + * 在当前会话上登录id, 并指定所有登录参数Model + * @param loginId 登录id,建议的类型:(long | int | String) + * @param loginModel 此次登录的参数Model + */ + @Deprecated + public static void setLoginId(Object loginId, SaLoginModel loginModel) { + stpLogic.login(loginId, loginModel); + } + + /** + *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变

+ * + * 会话注销,根据账号id (踢人下线) + *

当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 + * @param loginId 账号id + */ + @Deprecated + public static void logoutByLoginId(Object loginId) { + stpLogic.kickout(loginId); + } + + /** + *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变

+ * + * 会话注销,根据账号id and 设备类型 (踢人下线) + *

当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2

+ * @param loginId 账号id + * @param device 设备类型 (填null代表注销所有设备类型) + */ + @Deprecated + public static void logoutByLoginId(Object loginId, String device) { + stpLogic.kickout(loginId, device); + } + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/AtController.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/AtController.java new file mode 100644 index 00000000..dc7c6f95 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/AtController.java @@ -0,0 +1,80 @@ +package com.pj.test; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import cn.dev33.satoken.annotation.SaCheckBasic; +import cn.dev33.satoken.annotation.SaCheckLogin; +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaCheckRole; +import cn.dev33.satoken.annotation.SaCheckSafe; +import cn.dev33.satoken.annotation.SaMode; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; + +/** + * 注解鉴权测试 + * @author kong + * + */ +@RestController +@RequestMapping("/at/") +public class AtController { + + // 登录认证,登录之后才可以进入方法 ---- http://localhost:8081/at/checkLogin + @SaCheckLogin + @RequestMapping("checkLogin") + public SaResult checkLogin() { + return SaResult.ok(); + } + + // 权限认证,具备user-add权限才可以进入方法 ---- http://localhost:8081/at/checkPermission + @SaCheckPermission("user-add") + @RequestMapping("checkPermission") + public SaResult checkPermission() { + return SaResult.ok(); + } + + // 权限认证,同时具备所有权限才可以进入 ---- http://localhost:8081/at/checkPermissionAnd + @SaCheckPermission({"user-add", "user-delete", "user-update"}) + @RequestMapping("checkPermissionAnd") + public SaResult checkPermissionAnd() { + return SaResult.ok(); + } + + // 权限认证,只要具备其中一个就可以进入 ---- http://localhost:8081/at/checkPermissionOr + @SaCheckPermission(value = {"user-add", "user-delete", "user-update"}, mode = SaMode.OR) + @RequestMapping("checkPermissionOr") + public SaResult checkPermissionOr() { + return SaResult.ok(); + } + + // 角色认证,只有具备admin角色才可以进入 ---- http://localhost:8081/at/checkRole + @SaCheckRole("admin") + @RequestMapping("checkRole") + public SaResult checkRole() { + return SaResult.ok(); + } + + // 完成二级认证 ---- http://localhost:8081/at/openSafe + @RequestMapping("openSafe") + public SaResult openSafe() { + StpUtil.openSafe(200); // 打开二级认证,有效期为200秒 + return SaResult.ok(); + } + + // 通过二级认证后才可以进入 ---- http://localhost:8081/at/checkSafe + @SaCheckSafe + @RequestMapping("checkSafe") + public SaResult checkSafe() { + return SaResult.ok(); + } + + // 通过Basic认证后才可以进入 ---- http://localhost:8081/at/checkBasic + @SaCheckBasic(account = "sa:123456") + @RequestMapping("checkBasic") + public SaResult checkBasic() { + return SaResult.ok(); + } + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/LoginController.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/LoginController.java new file mode 100644 index 00000000..ea94cfa7 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/LoginController.java @@ -0,0 +1,48 @@ +package com.pj.test; + +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; + +/** + * 登录测试 + * @author kong + * + */ +@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(); + } + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/StressTestController.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/StressTestController.java new file mode 100644 index 00000000..5c0b3ce1 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/StressTestController.java @@ -0,0 +1,61 @@ +package com.pj.test; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.pj.util.AjaxJson; +import com.pj.util.Ttime; + +import cn.dev33.satoken.stp.StpUtil; + +/** + * 压力测试 + * @author kong + * + */ +@RestController +@RequestMapping("/s-test/") +public class StressTestController { + + // 测试 浏览器访问: http://localhost:8081/s-test/login + // 测试前,请先将 is-read-cookie 配置为 false + @RequestMapping("login") + public AjaxJson login() { +// StpUtil.getTokenSession().logout(); +// StpUtil.logoutByLoginId(10001); + + int count = 10; // 循环多少轮 + int loginCount = 10000; // 每轮循环多少次 + + // 循环10次 取平均时间 + List list = new ArrayList<>(); + for (int i = 1; i <= count; i++) { + System.out.println("\n---------------------第" + i + "轮---------------------"); + Ttime t = new Ttime().start(); + // 每次登录的次数 + for (int j = 1; j <= loginCount; j++) { + StpUtil.login("1000" + j, "PC-" + j); + if(j % 1000 == 0) { + System.out.println("已登录:" + j); + } + } + t.end(); + list.add((t.returnMs() + 0.0) / 1000); + System.out.println("第" + i + "轮" + "用时:" + t.toString()); + } +// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size()); + + System.out.println("\n---------------------测试结果---------------------"); + System.out.println(list.size() + "次测试: " + list); + double ss = 0; + for (int i = 0; i < list.size(); i++) { + ss += list.get(i); + } + System.out.println("平均用时: " + ss / list.size()); + return AjaxJson.getSuccess(); + } + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java new file mode 100644 index 00000000..0712eb2d --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java @@ -0,0 +1,251 @@ +package com.pj.test; + +import java.util.Date; +import java.util.List; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.pj.util.AjaxJson; +import com.pj.util.Ttime; + +import cn.dev33.satoken.annotation.SaCheckLogin; +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaCheckRole; +import cn.dev33.satoken.annotation.SaMode; +import cn.dev33.satoken.session.SaSessionCustomUtil; +import cn.dev33.satoken.stp.SaTokenInfo; +import cn.dev33.satoken.stp.StpUtil; + +/** + * 测试专用Controller + * @author kong + * + */ +@RestController +@RequestMapping("/test/") +public class TestController { + + // 测试登录接口, 浏览器访问: http://localhost:8081/test/login + @RequestMapping("login") + public AjaxJson login(@RequestParam(defaultValue="10001") String id) { + System.out.println("======================= 进入方法,测试登录接口 ========================= "); + System.out.println("当前会话的token:" + StpUtil.getTokenValue()); + System.out.println("当前是否登录:" + StpUtil.isLogin()); + System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull()); + + StpUtil.login(id); // 在当前会话登录此账号 + System.out.println("登录成功"); + System.out.println("当前是否登录:" + StpUtil.isLogin()); + System.out.println("当前登录账号:" + StpUtil.getLoginId()); +// System.out.println("当前登录账号并转为int:" + StpUtil.getLoginIdAsInt()); + System.out.println("当前登录设备:" + StpUtil.getLoginDevice()); +// System.out.println("当前token信息:" + StpUtil.getTokenInfo()); + + return AjaxJson.getSuccess(); + } + + // 测试退出登录 , 浏览器访问: http://localhost:8081/test/logout + @RequestMapping("logout") + public AjaxJson logout() { + StpUtil.logout(); +// StpUtil.logoutByLoginId(10001); + return AjaxJson.getSuccess(); + } + + // 测试角色接口, 浏览器访问: http://localhost:8081/test/testRole + @RequestMapping("testRole") + public AjaxJson testRole() { + System.out.println("======================= 进入方法,测试角色接口 ========================= "); + + System.out.println("是否具有角色标识 user " + StpUtil.hasRole("user")); + System.out.println("是否具有角色标识 admin " + StpUtil.hasRole("admin")); + + System.out.println("没有admin权限就抛出异常"); + StpUtil.checkRole("admin"); + + System.out.println("在【admin、user】中只要拥有一个就不会抛出异常"); + StpUtil.checkRoleOr("admin", "user"); + + System.out.println("在【admin、user】中必须全部拥有才不会抛出异常"); + StpUtil.checkRoleAnd("admin", "user"); + + System.out.println("角色测试通过"); + + return AjaxJson.getSuccess(); + } + + // 测试权限接口, 浏览器访问: http://localhost:8081/test/testJur + @RequestMapping("testJur") + public AjaxJson testJur() { + System.out.println("======================= 进入方法,测试权限接口 ========================= "); + + System.out.println("是否具有权限101" + StpUtil.hasPermission("101")); + System.out.println("是否具有权限user-add" + StpUtil.hasPermission("user-add")); + System.out.println("是否具有权限article-get" + StpUtil.hasPermission("article-get")); + + System.out.println("没有user-add权限就抛出异常"); + StpUtil.checkPermission("user-add"); + + System.out.println("在【101、102】中只要拥有一个就不会抛出异常"); + StpUtil.checkPermissionOr("101", "102"); + + System.out.println("在【101、102】中必须全部拥有才不会抛出异常"); + StpUtil.checkPermissionAnd("101", "102"); + + System.out.println("权限测试通过"); + + return AjaxJson.getSuccess(); + } + + // 测试会话session接口, 浏览器访问: http://localhost:8081/test/session + @RequestMapping("session") + public AjaxJson session() throws JsonProcessingException { + System.out.println("======================= 进入方法,测试会话session接口 ========================= "); + System.out.println("当前是否登录:" + StpUtil.isLogin()); + System.out.println("当前登录账号session的id" + StpUtil.getSession().getId()); + System.out.println("当前登录账号session的id" + StpUtil.getSession().getId()); + System.out.println("测试取值name:" + StpUtil.getSession().get("name")); + StpUtil.getSession().set("name", new Date()); // 写入一个值 + System.out.println("测试取值name:" + StpUtil.getSession().get("name")); + System.out.println( new ObjectMapper().writeValueAsString(StpUtil.getSession())); + return AjaxJson.getSuccess(); + } + + // 测试自定义session接口, 浏览器访问: http://localhost:8081/test/session2 + @RequestMapping("session2") + public AjaxJson session2() { + System.out.println("======================= 进入方法,测试自定义session接口 ========================= "); + // 自定义session就是无需登录也可以使用 的session :比如拿用户的手机号当做 key, 来获取 session + System.out.println("自定义 session的id为:" + SaSessionCustomUtil.getSessionById("1895544896").getId()); + System.out.println("测试取值name:" + SaSessionCustomUtil.getSessionById("1895544896").get("name")); + SaSessionCustomUtil.getSessionById("1895544896").set("name", "张三"); // 写入值 + System.out.println("测试取值name:" + SaSessionCustomUtil.getSessionById("1895544896").get("name")); + System.out.println("测试取值name:" + SaSessionCustomUtil.getSessionById("1895544896").get("name")); + return AjaxJson.getSuccess(); + } + + // ---------- + // 测试token专属session, 浏览器访问: http://localhost:8081/test/getTokenSession + @RequestMapping("getTokenSession") + public AjaxJson getTokenSession() { + System.out.println("======================= 进入方法,测试会话session接口 ========================= "); + System.out.println("当前是否登录:" + StpUtil.isLogin()); + System.out.println("当前token专属session: " + StpUtil.getTokenSession().getId()); + + System.out.println("测试取值name:" + StpUtil.getTokenSession().get("name")); + StpUtil.getTokenSession().set("name", "张三"); // 写入一个值 + System.out.println("测试取值name:" + StpUtil.getTokenSession().get("name")); + + return AjaxJson.getSuccess(); + } + + // 打印当前token信息, 浏览器访问: http://localhost:8081/test/tokenInfo + @RequestMapping("tokenInfo") + public AjaxJson tokenInfo() { + System.out.println("======================= 进入方法,打印当前token信息 ========================= "); + SaTokenInfo tokenInfo = StpUtil.getTokenInfo(); + System.out.println(tokenInfo); + return AjaxJson.getSuccessData(tokenInfo); + } + + // 测试注解式鉴权, 浏览器访问: http://localhost:8081/test/atCheck + @SaCheckLogin // 注解式鉴权:当前会话必须登录才能通过 + @SaCheckRole("super-admin") // 注解式鉴权:当前会话必须具有指定角色标识才能通过 + @SaCheckPermission("user-add") // 注解式鉴权:当前会话必须具有指定权限才能通过 + @RequestMapping("atCheck") + public AjaxJson atCheck() { + System.out.println("======================= 进入方法,测试注解鉴权接口 ========================= "); + System.out.println("只有通过注解鉴权,才能进入此方法"); +// StpUtil.checkActivityTimeout(); +// StpUtil.updateLastActivityToNow(); + return AjaxJson.getSuccess(); + } + + // 测试注解式鉴权, 浏览器访问: http://localhost:8081/test/atJurOr + @RequestMapping("atJurOr") + @SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR) // 注解式鉴权:只要具有其中一个权限即可通过校验 + public AjaxJson atJurOr() { + return AjaxJson.getSuccessData("用户信息"); + } + + // [活动时间] 续签: http://localhost:8081/test/rene + @RequestMapping("rene") + public AjaxJson rene() { + StpUtil.checkActivityTimeout(); + StpUtil.updateLastActivityToNow(); + return AjaxJson.getSuccess("续签成功"); + } + + // 测试踢人下线 浏览器访问: http://localhost:8081/test/kickOut + @RequestMapping("kickOut") + public AjaxJson kickOut() { + // 先登录上 + StpUtil.login(10001); + // 踢下线 + StpUtil.kickout(10001); + // 再尝试获取 + StpUtil.getLoginId(); + // 返回 + return AjaxJson.getSuccess(); + } + + // 测试登录接口, 按照设备登录, 浏览器访问: http://localhost:8081/test/login2 + @RequestMapping("login2") + public AjaxJson login2(@RequestParam(defaultValue="10001") String id, @RequestParam(defaultValue="PC") String device) { + StpUtil.login(id, device); + return AjaxJson.getSuccess(); + } + + // 测试身份临时切换: http://localhost:8081/test/switchTo + @RequestMapping("switchTo") + public AjaxJson switchTo() { + System.out.println("当前会话身份:" + StpUtil.getLoginIdDefaultNull()); + System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch()); + StpUtil.switchTo(10044, () -> { + System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch()); + System.out.println("当前会话身份已被切换为:" + StpUtil.getLoginId()); + }); + System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch()); + return AjaxJson.getSuccess(); + } + + // 测试会话治理 浏览器访问: http://localhost:8081/test/search + @RequestMapping("search") + public AjaxJson search() { + System.out.println("--------------"); + Ttime t = new Ttime().start(); + List tokenValue = StpUtil.searchTokenValue("8feb8265f773", 0, 10); + for (String v : tokenValue) { +// SaSession session = StpUtil.getSessionBySessionId(sid); + System.out.println(v); + } + System.out.println("用时:" + t.end().toString()); + return AjaxJson.getSuccess(); + } + + // 测试指定设备登录 浏览器访问: http://localhost:8081/test/loginByDevice + @RequestMapping("loginByDevice") + public AjaxJson loginByDevice() { + System.out.println("--------------"); + StpUtil.login(10001, "PC"); + return AjaxJson.getSuccessData("登录成功"); + } + + // 测试 浏览器访问: http://localhost:8081/test/test + @RequestMapping("test") + public AjaxJson test() { + System.out.println("------------进来了"); + return AjaxJson.getSuccess(); + } + + // 测试 浏览器访问: http://localhost:8081/test/test2 + @RequestMapping("test2") + public AjaxJson test2() { + return AjaxJson.getSuccess(); + } + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/AjaxJson.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/AjaxJson.java new file mode 100644 index 00000000..768d0578 --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/AjaxJson.java @@ -0,0 +1,162 @@ +package com.pj.util; + +import java.io.Serializable; +import java.util.List; + + +/** + * ajax请求返回Json格式数据的封装 + */ +public class AjaxJson implements Serializable{ + + private static final long serialVersionUID = 1L; // 序列化版本号 + + public static final int CODE_SUCCESS = 200; // 成功状态码 + public static final int CODE_ERROR = 500; // 错误状态码 + public static final int CODE_WARNING = 501; // 警告状态码 + public static final int CODE_NOT_JUR = 403; // 无权限状态码 + public static final int CODE_NOT_LOGIN = 401; // 未登录状态码 + public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码 + + public int code; // 状态码 + public String msg; // 描述信息 + public Object data; // 携带对象 + public Long dataCount; // 数据总数,用于分页 + + /** + * 返回code + * @return + */ + public int getCode() { + return this.code; + } + + /** + * 给msg赋值,连缀风格 + */ + public AjaxJson setMsg(String msg) { + this.msg = msg; + return this; + } + public String getMsg() { + return this.msg; + } + + /** + * 给data赋值,连缀风格 + */ + public AjaxJson setData(Object data) { + this.data = data; + return this; + } + + /** + * 将data还原为指定类型并返回 + */ + @SuppressWarnings("unchecked") + public T getData(Class cs) { + return (T) data; + } + + // ============================ 构建 ================================== + + public AjaxJson(int code, String msg, Object data, Long dataCount) { + this.code = code; + this.msg = msg; + this.data = data; + this.dataCount = dataCount; + } + + // 返回成功 + public static AjaxJson getSuccess() { + return new AjaxJson(CODE_SUCCESS, "ok", null, null); + } + public static AjaxJson getSuccess(String msg) { + return new AjaxJson(CODE_SUCCESS, msg, null, null); + } + public static AjaxJson getSuccess(String msg, Object data) { + return new AjaxJson(CODE_SUCCESS, msg, data, null); + } + public static AjaxJson getSuccessData(Object data) { + return new AjaxJson(CODE_SUCCESS, "ok", data, null); + } + public static AjaxJson getSuccessArray(Object... data) { + return new AjaxJson(CODE_SUCCESS, "ok", data, null); + } + + // 返回失败 + public static AjaxJson getError() { + return new AjaxJson(CODE_ERROR, "error", null, null); + } + public static AjaxJson getError(String msg) { + return new AjaxJson(CODE_ERROR, msg, null, null); + } + + // 返回警告 + public static AjaxJson getWarning() { + return new AjaxJson(CODE_ERROR, "warning", null, null); + } + public static AjaxJson getWarning(String msg) { + return new AjaxJson(CODE_WARNING, msg, null, null); + } + + // 返回未登录 + public static AjaxJson getNotLogin() { + return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null); + } + + // 返回没有权限的 + public static AjaxJson getNotJur(String msg) { + return new AjaxJson(CODE_NOT_JUR, msg, null, null); + } + + // 返回一个自定义状态码的 + public static AjaxJson get(int code, String msg){ + return new AjaxJson(code, msg, null, null); + } + + // 返回分页和数据的 + public static AjaxJson getPageData(Long dataCount, Object data){ + return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount); + } + + // 返回,根据受影响行数的(大于0=ok,小于0=error) + public static AjaxJson getByLine(int line){ + if(line > 0){ + return getSuccess("ok", line); + } + return getError("error").setData(line); + } + + // 返回,根据布尔值来确定最终结果的 (true=ok,false=error) + public static AjaxJson getByBoolean(boolean b){ + return b ? getSuccess("ok") : getError("error"); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @SuppressWarnings("rawtypes") + @Override + public String toString() { + String data_string = null; + if(data == null){ + + } else if(data instanceof List){ + data_string = "List(length=" + ((List)data).size() + ")"; + } else { + data_string = data.toString(); + } + return "{" + + "\"code\": " + this.getCode() + + ", \"msg\": \"" + this.getMsg() + "\"" + + ", \"data\": " + data_string + + ", \"dataCount\": " + dataCount + + "}"; + } + + + + + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/Ttime.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/Ttime.java new file mode 100644 index 00000000..cb17dcaa --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/Ttime.java @@ -0,0 +1,63 @@ +package com.pj.util; + + +/** + * 用于测试用时 + * @author kong + * + */ +public class Ttime { + + private long start=0; //开始时间 + private long end=0; //结束时间 + + public static Ttime t = new Ttime(); //static快捷使用 + + /** + * 开始计时 + * @return + */ + public Ttime start() { + start=System.currentTimeMillis(); + return this; + } + + + /** + * 结束计时 + */ + public Ttime end() { + end=System.currentTimeMillis(); + return this; + } + + + /** + * 返回所用毫秒数 + */ + public long returnMs() { + return end-start; + } + + /** + * 格式化输出结果 + */ + public void outTime() { + System.out.println(this.toString()); + } + + /** + * 结束并格式化输出结果 + */ + public void endOutTime() { + this.end().outTime(); + } + + @Override + public String toString() { + return (returnMs() + 0.0) / 1000 + "s"; // 格式化为:0.01s + } + + + +} diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml new file mode 100644 index 00000000..cb63067a --- /dev/null +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml @@ -0,0 +1,49 @@ +# 端口 +server: + port: 8081 + +# sa-token配置 +sa-token: + # token名称 (同时也是cookie名称) + token-name: satoken + # token有效期,单位s 默认30天, -1代表永不过期 + timeout: 2592000 + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + activity-timeout: -1 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: true + # token风格 + token-style: uuid + # 是否输出操作日志 + is-log: false + +spring: + # redis配置 + redis: + # Redis数据库索引(默认为0) + database: 0 + # Redis服务器地址 + host: 127.0.0.1 + # Redis服务器连接端口 + port: 6379 + # Redis服务器连接密码(默认为空) + password: + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池最大连接数 + max-active: 200 + # 连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + # 连接池中的最大空闲连接 + max-idle: 10 + # 连接池中的最小空闲连接 + min-idle: 0 + + + + + \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-springboot/pom.xml b/sa-token-demo/sa-token-demo-springboot/pom.xml index 5c443d13..afaf8993 100644 --- a/sa-token-demo/sa-token-demo-springboot/pom.xml +++ b/sa-token-demo/sa-token-demo-springboot/pom.xml @@ -17,7 +17,7 @@ - 1.30.0.RC + 1.30.0 @@ -39,26 +39,6 @@ ${sa-token-version} - - - - - - - - - - - diff --git a/sa-token-demo/sa-token-demo-sso-server/pom.xml b/sa-token-demo/sa-token-demo-sso-server/pom.xml index af57f8b0..18cdd8d9 100644 --- a/sa-token-demo/sa-token-demo-sso-server/pom.xml +++ b/sa-token-demo/sa-token-demo-sso-server/pom.xml @@ -16,7 +16,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-sso1-client/pom.xml b/sa-token-demo/sa-token-demo-sso1-client/pom.xml index f1a74e8d..c21a3263 100644 --- a/sa-token-demo/sa-token-demo-sso1-client/pom.xml +++ b/sa-token-demo/sa-token-demo-sso1-client/pom.xml @@ -16,7 +16,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-sso2-client/pom.xml b/sa-token-demo/sa-token-demo-sso2-client/pom.xml index dd655d54..da44a372 100644 --- a/sa-token-demo/sa-token-demo-sso2-client/pom.xml +++ b/sa-token-demo/sa-token-demo-sso2-client/pom.xml @@ -16,7 +16,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-sso3-client/pom.xml b/sa-token-demo/sa-token-demo-sso3-client/pom.xml index ee90d827..9af2f62c 100644 --- a/sa-token-demo/sa-token-demo-sso3-client/pom.xml +++ b/sa-token-demo/sa-token-demo-sso3-client/pom.xml @@ -16,7 +16,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-thymeleaf/pom.xml b/sa-token-demo/sa-token-demo-thymeleaf/pom.xml index ec496107..3227b68d 100644 --- a/sa-token-demo/sa-token-demo-thymeleaf/pom.xml +++ b/sa-token-demo/sa-token-demo-thymeleaf/pom.xml @@ -16,7 +16,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-webflux/pom.xml b/sa-token-demo/sa-token-demo-webflux/pom.xml index 2eac97eb..d71464d7 100644 --- a/sa-token-demo/sa-token-demo-webflux/pom.xml +++ b/sa-token-demo/sa-token-demo-webflux/pom.xml @@ -16,7 +16,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-websocket-spring/pom.xml b/sa-token-demo/sa-token-demo-websocket-spring/pom.xml index 08f862bc..e0a4b2a7 100644 --- a/sa-token-demo/sa-token-demo-websocket-spring/pom.xml +++ b/sa-token-demo/sa-token-demo-websocket-spring/pom.xml @@ -17,7 +17,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-demo/sa-token-demo-websocket/pom.xml b/sa-token-demo/sa-token-demo-websocket/pom.xml index 5ddbb0b5..b18f7b89 100644 --- a/sa-token-demo/sa-token-demo-websocket/pom.xml +++ b/sa-token-demo/sa-token-demo-websocket/pom.xml @@ -17,7 +17,7 @@ - 1.30.0.RC + 1.30.0 diff --git a/sa-token-doc/doc/README.md b/sa-token-doc/doc/README.md index 98dd7a87..b9225ef2 100644 --- a/sa-token-doc/doc/README.md +++ b/sa-token-doc/doc/README.md @@ -1,7 +1,7 @@

logo

-

Sa-Token v1.30.0.RC

+

Sa-Token v1.30.0

一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!

@@ -122,10 +122,6 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号

-[![giteye-chart](https://chart.giteye.net/gitee/dromara/sa-token/77YQZ6UK.png 'Gitee')](https://giteye.net/chart/77YQZ6UK) - -

- [![github-chart](https://starchart.cc/dromara/sa-token.svg 'GitHub')](https://starchart.cc/dromara/sa-token) 如果 Sa-Token 帮助到了您,希望您可以为其点上一个 `star`: @@ -133,19 +129,10 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号 [GitHub](https://github.com/dromara/sa-token) - - ## 使用Sa-Token的开源项目 参考:[Sa-Token 生态](/more/link) -## 贡献者名单 -感谢每一个为 Sa-Token 贡献代码的小伙伴 - -

- -[![Giteye chart](https://chart.giteye.net/gitee/dromara/sa-token/CGZ7GT8E.png)](https://giteye.net/chart/CGZ7GT8E) - ## 交流群 QQ交流群:1群:1002350610 (已满) 、 2群:614714762 [点击加入](https://jq.qq.com/?_wv=1027&k=b759RZrL) diff --git a/sa-token-doc/doc/_sidebar.md b/sa-token-doc/doc/_sidebar.md index a2ebd112..1a19d128 100644 --- a/sa-token-doc/doc/_sidebar.md +++ b/sa-token-doc/doc/_sidebar.md @@ -85,6 +85,7 @@ - [Token有效期详解](/fun/token-timeout) - [Session模型详解](/fun/session-model) - [TokenInfo参数详解](/fun/token-info) + - [异常细分状态码](/fun/exception-code) - [解决反向代理 uri 丢失的问题](/fun/curr-domain) - [参考:把权限放在缓存里](/fun/jur-cache) - [解决跨域问题](/fun/cors-filter) diff --git a/sa-token-doc/doc/fun/exception-code.md b/sa-token-doc/doc/fun/exception-code.md new file mode 100644 index 00000000..250169dc --- /dev/null +++ b/sa-token-doc/doc/fun/exception-code.md @@ -0,0 +1,107 @@ +# 异常细分状态码 + +--- + +### 获取异常细分状态码 + +Sa-Token 中的基础异常类是 `SaTokenException`,在此基础上,又针对一些特定场景划分出诸如 `NotLoginException`、`NotPermissionException` 等。 + +但是框架中异常抛出点远远多于异常种类的划分,比如在 SSO 插件中,[ redirect 重定向地址无效 ] 和 [ ticket 参数值无效 ] 都会导致 SSO 授权的失败, +但是它们抛出的异常都是 `SaSsoException`,如果你需要对这两种异常情形做出不同的处理,仅仅判断异常的 ClassType 显然不够。 + +为了解决上述需求,Sa-Token 对每个异常抛出点都会指定一个特定的 code 值,就像这样: + +``` java +if(SaFoxUtil.isUrl(url) == false) { + throw new SaSsoException("无效redirect:" + url).setCode(SaSsoExceptionCode.CODE_20001); +} +``` + +就像是打上一个特定的标记,不同异常情形标记的 code 码值也会不同,这就为你精细化异常处理提供了前提。 + +要在捕获异常时获取这个 code 码也非常简单: + +``` java +@RestControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(SaTokenException.class) + public SaResult handlerSaTokenException(SaTokenException e) { + + // 根据不同异常细分状态码返回不同的提示 + if(e.getCode() == 20001) { + return SaResult.error("redirect 重定向 url 是一个无效地址"); + } + if(e.getCode() == 20002) { + return SaResult.error("redirect 重定向 url 不在 allowUrl 允许的范围内"); + } + if(e.getCode() == 20004) { + return SaResult.error("提供的 ticket 是无效的"); + } + // 更多 code 码判断 ... + + // 默认的提示 + return SaResult.error("服务器繁忙,请稍后重试..."); + } +} +``` + +SaToken 中的所有异常都是继承于 `SaTokenException` 的,也就是说,所有异常你都可以通过 `e.getCode()` 的方式获取对应的异常细分状态码。 + + + + + +### 异常细分状态码-参照表 + +!> 目前仅对 sso 插件和 jwt 插件做了异常状态码细分,后续版本升级会支持更多模块 + +**sa-token-code 核心包:** + +| code码值 | 含义 | +| :-------- | :-------- | +| -1 | 代表这个异常在抛出时未指定异常细分状态码 | + + +**sa-token-sso 单点登录相关:** + +| code码值 | 含义 | +| :-------- | :-------- | +| 20001 | `redirect` 重定向 url 是一个无效地址 | +| 20002 | `redirect` 重定向 url 不在 allowUrl 允许的范围内 | +| 20003 | 接口调用方提供的 `secretkey` 秘钥无效 | +| 20004 | 提供的 `ticket` 是无效的 | +| 20005 | 在模式三下,sso-client 调用 sso-server 端 校验ticket接口 时,得到的响应是校验失败 | +| 20006 | 在模式三下,sso-client 调用 sso-server 端 单点注销接口 时,得到的响应是注销失败 | +| 20007 | http 请求调用 提供的 `timestamp` 与当前时间的差距超出允许的范围 | +| 20008 | http 请求调用 提供的 `sign` 无效 | +| 20009 | 本地系统没有配置 `secretkey` 字段 | + + + +**sa-token-jwt 插件相关:** + +| code码值 | 含义 | +| :-------- | :-------- | +| 40101 | 对 jwt 字符串解析失败 | +| 40102 | 此 jwt 的签名无效 | +| 40103 | 此 jwt 的 `loginType` 字段不符合预期 | +| 40104 | 此 jwt 已超时 | + + + + + + + + + + + + + + + + + + + diff --git a/sa-token-doc/doc/index.html b/sa-token-doc/doc/index.html index 07b9130f..aa11b204 100644 --- a/sa-token-doc/doc/index.html +++ b/sa-token-doc/doc/index.html @@ -20,7 +20,7 @@

Sa-Token

- v1.30.0.RC + v1.30.0
@@ -114,7 +114,7 @@