diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaFunction.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaFunction.java new file mode 100644 index 00000000..4798fa8b --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaFunction.java @@ -0,0 +1,15 @@ +package cn.dev33.satoken.stp; + +/** + * 模拟身份方法的辅助类 + * @author kong + * + */ +public interface SaFunction { + + /** + * 执行的方法 + */ + public void run(); + +} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java index 33ebe5b2..89a7441d 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -44,6 +44,24 @@ public class StpLogic { this.loginKey = loginKey; } + /** + * 获取当前StpLogin的loginKey + * @return 当前StpLogin的loginKey + */ + public String getLoginKey(){ + return loginKey; + } + + /** + * 写入当前StpLogin的loginKey + * @param loginKey loginKey + * @return 对象自身 + */ + public StpLogic setLoginKey(String loginKey){ + this.loginKey = loginKey; + return this; + } + // =================== 获取token 相关 =================== @@ -77,8 +95,8 @@ public class StpLogic { String tokenValue = null; // 1. 尝试从request里读取 - if(request.getAttribute(SaTokenConsts.JUST_CREATED_SAVE_KEY) != null) { - tokenValue = String.valueOf(request.getAttribute(SaTokenConsts.JUST_CREATED_SAVE_KEY)); + if(request.getAttribute(getJustCreatedSaveKey()) != null) { + tokenValue = String.valueOf(request.getAttribute(getJustCreatedSaveKey())); } // 2. 尝试从请求体里面读取 if(tokenValue == null && config.getIsReadBody() == true){ @@ -100,14 +118,6 @@ public class StpLogic { return tokenValue; } - /** - * 获取当前StpLogin的loginKey - * @return 当前StpLogin的loginKey - */ - public String getLoginKey(){ - return loginKey; - } - /** * 获取当前会话的token信息 * @return token信息 @@ -191,7 +201,7 @@ public class StpLogic { // ------ 4. 持久化其它数据 dao.setValue(getKeyTokenValue(tokenValue), String.valueOf(loginId), config.getTimeout()); // token -> uid - request.setAttribute(SaTokenConsts.JUST_CREATED_SAVE_KEY, tokenValue); // 将token保存到本次request里 + request.setAttribute(getJustCreatedSaveKey(), tokenValue); // 将token保存到本次request里 setLastActivityToNow(tokenValue); // 写入 [最后操作时间] if(config.getIsReadCookie() == true){ // cookie注入 SaTokenManager.getSaTokenCookie().addCookie(SaTokenManager.getSaTokenServlet().getResponse(), getTokenName(), tokenValue, "/", (int)config.getTimeout()); @@ -303,6 +313,10 @@ public class StpLogic { * @return 账号id */ public Object getLoginId() { + // 如果正在[临时身份切换] + if(isSwitch()) { + return getSwitchLoginId(); + } // 如果获取不到token,则抛出:无token String tokenValue = getTokenValue(); if(tokenValue == null) { @@ -363,6 +377,10 @@ public class StpLogic { * @return 账号id */ public Object getLoginIdDefaultNull() { + // 如果正在[临时身份切换] + if(isSwitch()) { + return getSwitchLoginId(); + } // 如果连token都是空的,则直接返回 String tokenValue = getTokenValue(); if(tokenValue == null) { @@ -536,7 +554,7 @@ public class StpLogic { if(tokenValue == null || Objects.equals(tokenValue, "")) { // 随机一个token送给ta tokenValue = createTokenValue(null); - SaTokenManager.getSaTokenServlet().getRequest().setAttribute(SaTokenConsts.JUST_CREATED_SAVE_KEY, tokenValue); + SaTokenManager.getSaTokenServlet().getRequest().setAttribute(getJustCreatedSaveKey(), tokenValue); setLastActivityToNow(tokenValue); // 写入 [最后操作时间] if(getConfig().getIsReadCookie() == true){ // cookie注入 SaTokenManager.getSaTokenCookie().addCookie(SaTokenManager.getSaTokenServlet().getResponse(), getTokenName(), tokenValue, "/", (int)getConfig().getTimeout()); @@ -1039,6 +1057,20 @@ public class StpLogic { return getConfig().getTokenName() + ":" + loginKey + ":last-activity:" + tokenValue; } + /** + * 在进行身份切换时,使用的存储key + */ + public String getKeySwitch() { + return SaTokenConsts.SWITCH_TO_SAVE_KEY + getLoginKey(); + } + + /** + * 如果token为本次请求新创建的,则以此字符串为key存储在当前request中 + */ + public String getJustCreatedSaveKey() { + return SaTokenConsts.JUST_CREATED_SAVE_KEY + getLoginKey(); + } + // =================== Bean对象代理 =================== @@ -1052,7 +1084,7 @@ public class StpLogic { } - // =================== 注解鉴权 =================== + // =================== 其它方法 =================== /** * 对一个Method对象进行注解检查(注解鉴权内部实现) @@ -1111,6 +1143,55 @@ public class StpLogic { // 验证通过 } + + + // =================== 身份切换 =================== + + /** + * 临时切换身份为指定loginId + * @param loginId 指定loginId + */ + public void switchTo(Object loginId) { + SaTokenManager.getSaTokenServlet().getRequest().setAttribute(getKeySwitch(), loginId); + } + + /** + * 结束临时切换身份 + */ + public void endSwitch() { + SaTokenManager.getSaTokenServlet().getRequest().removeAttribute(getKeySwitch()); + } + + /** + * 当前是否正处于[身份临时切换]中 + */ + public boolean isSwitch() { + return SaTokenManager.getSaTokenServlet().getRequest().getAttribute(getKeySwitch()) != null; + } + + /** + * 返回[身份临时切换]的loginId + */ + public Object getSwitchLoginId() { + return SaTokenManager.getSaTokenServlet().getRequest().getAttribute(getKeySwitch()); + } + + /** + * 在一个代码段里方法内,临时切换身份为指定loginId + * @param loginId 指定loginId + */ + public void switchTo(Object loginId, SaFunction function) { + try { + switchTo(loginId); + function.run(); + } catch (Exception e) { + throw e; + } finally { + endSwitch(); + } + } + + diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java index 8c3b0953..6e9219ca 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java @@ -14,7 +14,16 @@ public class StpUtil { * 底层的 StpLogic 对象 */ public static StpLogic stpLogic = new StpLogic("login"); + + /** + * 获取当前StpLogin的loginKey + * @return 当前StpLogin的loginKey + */ + public static String getLoginKey(){ + return stpLogic.getLoginKey(); + } + // =================== 获取token 相关 =================== @@ -34,14 +43,6 @@ public class StpUtil { return stpLogic.getTokenValue(); } - /** - * 获取当前StpLogin的loginKey - * @return 当前StpLogin的loginKey - */ - public static String getLoginKey(){ - return stpLogic.getLoginKey(); - } - /** * 获取当前会话的token信息 * @return token信息 @@ -486,4 +487,37 @@ public class StpUtil { } + // =================== 身份切换 =================== + + /** + * 临时切换身份为指定loginId + * @param loginId 指定loginId + */ + public static void switchTo(Object loginId) { + stpLogic.switchTo(loginId); + } + + /** + * 结束临时切换身份 + */ + public static void endSwitch() { + stpLogic.endSwitch(); + } + + /** + * 当前是否正处于[身份临时切换]中 + */ + public static boolean isSwitch() { + return stpLogic.isSwitch(); + } + + /** + * 在一个代码段里方法内,临时切换身份为指定loginId + * @param loginId 指定loginId + */ + public static void switchTo(Object loginId, SaFunction function) { + stpLogic.switchTo(loginId, function); + } + + } 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 0d85cec2..ea2efb25 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 @@ -18,7 +18,7 @@ public class SaTokenConsts { public static final String GITHUB_URL = "https://github.com/click33/sa-token"; /** - * 如果token为本次请求新创建的,则以此字符串为key存储在当前request中 JUST_CREATED_SAVE_KEY + * 如果token为本次请求新创建的,则以此字符串为key存储在当前request中 */ public static final String JUST_CREATED_SAVE_KEY = "JUST_CREATED_SAVE_KEY_"; @@ -32,11 +32,10 @@ public class SaTokenConsts { */ public static final String DEFAULT_LOGIN_DEVICE = "default-device"; -// /** -// * 在用一个字符串存储多个token时,所使用的分隔符 -// */ -// public static final String MULTIPLE_TOKEN_SEPARATOR = ","; - + /** + * 在进行临时身份切换时使用的key + */ + public static final String SWITCH_TO_SAVE_KEY = "SWITCH_TO_SAVE_KEY_"; diff --git a/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java b/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java index 61e902b8..0d590e86 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java @@ -11,6 +11,7 @@ public class SaTokenDemoApplication { public static void main(String[] args) { SpringApplication.run(SaTokenDemoApplication.class, args); System.out.println("\n启动成功:sa-token配置如下:" + SaTokenManager.getConfig()); + } } \ No newline at end of file diff --git a/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java index 523e61a8..df4f41d7 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java @@ -145,7 +145,6 @@ public class TestController { return AjaxJson.getSuccess(); } - // 打印当前token信息, 浏览器访问: http://localhost:8081/test/tokenInfo @RequestMapping("tokenInfo") public AjaxJson tokenInfo() { @@ -203,6 +202,18 @@ public class TestController { 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") diff --git a/sa-token-doc/doc/use/mock-person.md b/sa-token-doc/doc/use/mock-person.md index 5fe426cb..c06aefe0 100644 --- a/sa-token-doc/doc/use/mock-person.md +++ b/sa-token-doc/doc/use/mock-person.md @@ -1,6 +1,7 @@ # 模拟他人 --- + - 以上介绍的api都是操作当前账号,对当前账号进行各种鉴权操作,你可能会问,我能不能对别的账号进行一些操作? - 比如:查看账号`10001`有无某个权限码、获取id账号为`10002`的用户`session`,等等... - `sa-token`在api设计时充分考虑了这一点,暴露出多个api进行此类操作 @@ -8,23 +9,47 @@ ## 有关操作其它账号的api -#### StpUtil.getTokenValueByLoginId(Object loginId) -- 获取指定`loginId`的`tokenValue`值 - -#### StpUtil.logoutByLoginId(Object loginId) -- 指定`loginId`的会话注销登录(踢人下线) - -#### StpUtil.getSessionByLoginId(Object loginId) -- 获取指定`loginId`的`session`(如果此id尚未创建`session`, 则返回`null`) -- 类似API还有: - - `StpUtil.getSessionByLoginId(Object loginId, boolean isCreate)` 获取当前会话登录id, `isCreate`代表指定是否在无`session`的情况下新建并返回 - -#### StpUtil.hasRole(Object loginId, String role) -- 指定`loginId`是否含有指定角色 - -#### StpUtil.hasPermission(Object loginId, String permission) -- 指定`loginId`是否含有指定权限 +``` java +StpUtil.getTokenValueByLoginId(10001); // 获取指定账号10001的`tokenValue`值 +StpUtil.logoutByLoginId(10001); // 将账号10001的会话注销登录(踢人下线) +StpUtil.getSessionByLoginId(10001); // 获取账号10001的Session对象, 如果session尚未创建, 则新建并返回 +StpUtil.getSessionByLoginId(10001, false); // 获取账号10001的Session对象, 如果session尚未创建, 则返回null +StpUtil.hasRole(10001, false); // 获取账号10001是否含有指定角色标识 +StpUtil.hasPermission(10001, false); // 获取账号10001是否含有指定权限码 +``` +## 临时身份切换 + +有时候,我们需要直接将当前会话的身份切换为其它账号,比如: +``` java +StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号 +StpUtil.getLoginId(); // 此时再调用此方法会返回 10044 +StpUtil.endSwitch(); // 结束 [身份临时切换] +``` + +你还可以: 直接在一个代码段里方法内,临时切换身份为指定loginId (此方式无需手动调用`StpUtil.endSwitch()`关闭身份切换) +``` java +System.out.println("------- [身份临时切换]调用开始..."); +StpUtil.switchTo(10044, new SaFunction() { + @Override + public void run() { + System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch()); + System.out.println("获取当前登录账号id: " + StpUtil.getLoginId()); + } +}); +System.out.println("------- [身份临时切换]调用结束..."); +``` + +如果你使用的JDK版本是1.8或以上,上面这一坨可以简写为以下形式: +``` java +System.out.println("------- [身份临时切换]调用开始..."); +StpUtil.switchTo(10044, () -> { + System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch()); + System.out.println("获取当前登录账号id: " + StpUtil.getLoginId()); +}); +System.out.println("------- [身份临时切换]调用结束..."); +``` + diff --git a/sa-token-doc/doc/use/route-check.md b/sa-token-doc/doc/use/route-check.md index 56227463..f8799e9e 100644 --- a/sa-token-doc/doc/use/route-check.md +++ b/sa-token-doc/doc/use/route-check.md @@ -44,7 +44,7 @@ public class MySaTokenConfig implements WebMvcConfigurer { registry.addInterceptor(SaRouteInterceptor.createPermissionVal("user:add", "user:deelete")).addPathPatterns("/UserController/**"); // 注册一个自定义认证拦截器 (可以写任意认证代码) - registry.addInterceptor(new SaRouteInterceptor(new SaFunction() { + registry.addInterceptor(new SaRouteInterceptor(new SaRouteFunction() { @Override public void run(HttpServletRequest request, HttpServletResponse response, Object handler) { // 你可以在这里写任意认证代码, 例如: StpUtil.checkLogin(); @@ -52,7 +52,7 @@ public class MySaTokenConfig implements WebMvcConfigurer { } })).addPathPatterns("/**"); - /** ------ 如果你使用的JDK版本是1.8或以上,上面那一坨可以简写为以下形式 ------ */ + /** ------ 如果你使用的JDK版本是1.8或以上,上面这一坨可以简写为以下形式 ------ */ // 注册一个自定义认证拦截器 (可以写任意认证代码) registry.addInterceptor(new SaRouteInterceptor((request, response, handler)->{ @@ -66,7 +66,7 @@ public class MySaTokenConfig implements WebMvcConfigurer { (你不必像上面的示例一样注册所有拦截器,只要按需注册即可 ) -## 3、所有拦截器示例 + diff --git a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaFunction.java b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteFunction.java similarity index 90% rename from sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaFunction.java rename to sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteFunction.java index 38ef002a..1408c7a0 100644 --- a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaFunction.java +++ b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteFunction.java @@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletResponse; * @author kong * */ -public interface SaFunction { +public interface SaRouteFunction { /** * 执行验证的方法 diff --git a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteInterceptor.java b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteInterceptor.java index a15801df..addadbc2 100644 --- a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteInterceptor.java +++ b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteInterceptor.java @@ -41,7 +41,7 @@ public class SaRouteInterceptor implements HandlerInterceptor { /** * 自定义模式下的执行函数 */ - private SaFunction function; + private SaRouteFunction function; /** @@ -131,14 +131,14 @@ public class SaRouteInterceptor implements HandlerInterceptor { /** * @return 自定义模式下的执行函数 */ - public SaFunction getFunction() { + public SaRouteFunction getFunction() { return function; } /** * @param function 设置自定义模式下的执行函数 */ - public SaRouteInterceptor setFunction(SaFunction function) { + public SaRouteInterceptor setFunction(SaRouteFunction function) { this.type = SaRouteInterceptor.CUSTOM; this.function = function; return this; @@ -171,7 +171,7 @@ public class SaRouteInterceptor implements HandlerInterceptor { * 创建 (默认为自定义认证) * @param function 自定义模式下的执行函数 */ - public SaRouteInterceptor(SaFunction function) { + public SaRouteInterceptor(SaRouteFunction function) { this(SaRouteInterceptor.CUSTOM, null, new String[0]); setFunction(function); } @@ -207,7 +207,7 @@ public class SaRouteInterceptor implements HandlerInterceptor { * @param function 自定义模式下的执行函数 * @return sa拦截器 */ - public static SaRouteInterceptor createCustomVal(SaFunction function) { + public static SaRouteInterceptor createCustomVal(SaRouteFunction function) { return new SaRouteInterceptor(function); }