From 432bfe36bf1f8190c48f13b7956eaaee4cab645b Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 23 Oct 2022 04:44:39 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=8C=E7=BA=A7=E8=AE=A4=E8=AF=81=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=8C=87=E5=AE=9A=E4=B8=9A=E5=8A=A1=E6=A0=87=E8=AF=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev33/satoken/annotation/SaCheckSafe.java | 10 +- .../satoken/exception/NotSafeException.java | 59 ++++++++- .../java/cn/dev33/satoken/stp/StpLogic.java | 117 ++++++++++++++++-- .../java/cn/dev33/satoken/stp/StpUtil.java | 53 ++++++++ .../cn/dev33/satoken/util/SaTokenConsts.java | 13 +- .../com/pj/cases/up/SafeAuthController.java | 42 ++++++- .../java/com/pj/current/GlobalException.java | 2 +- 7 files changed, 273 insertions(+), 23 deletions(-) diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaCheckSafe.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaCheckSafe.java index e2ad3ab4..a22f8bf7 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaCheckSafe.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaCheckSafe.java @@ -5,6 +5,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import cn.dev33.satoken.util.SaTokenConsts; + /** * 二级认证校验:必须二级认证之后才能进入该方法 * @@ -18,8 +20,14 @@ public @interface SaCheckSafe { /** * 多账号体系下所属的账号体系标识 - * @return see note + * @return / */ String type() default ""; + /** + * 要校验的服务 + * @return / + */ + String value() default SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE; + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/exception/NotSafeException.java b/sa-token-core/src/main/java/cn/dev33/satoken/exception/NotSafeException.java index 5a467fc5..1b1d2abc 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/exception/NotSafeException.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/exception/NotSafeException.java @@ -1,7 +1,7 @@ package cn.dev33.satoken.exception; /** - * 一个异常:代表会话未能通过二级认证 + * 一个异常:代表会话未能通过二级认证校验 * * @author kong */ @@ -13,13 +13,62 @@ public class NotSafeException extends SaTokenException { private static final long serialVersionUID = 6806129545290130144L; /** 异常提示语 */ - public static final String BE_MESSAGE = "二级认证失败"; + public static final String BE_MESSAGE = "二级认证校验失败"; /** - * 一个异常:代表会话未通过二级认证 + * 账号类型 */ - public NotSafeException() { - super(BE_MESSAGE); + private String loginType; + + /** + * 未通过校验的 Token 值 + */ + private Object tokenValue; + + /** + * 未通过校验的服务 + */ + private String service; + + /** + * 获取:账号类型 + * + * @return / + */ + public String getLoginType() { + return loginType; + } + + /** + * 获取: 未通过校验的 Token 值 + * + * @return / + */ + public Object getTokenValue() { + return tokenValue; + } + + /** + * 获取: 未通过校验的服务 + * + * @return / + */ + public Object getService() { + return service; + } + + /** + * 一个异常:代表会话未能通过二级认证校验 + * + * @param loginType 账号类型 + * @param tokenValue 未通过校验的 Token 值 + * @param service 未通过校验的服务 + */ + public NotSafeException(String loginType, String tokenValue, String service) { + super(BE_MESSAGE + ":" + service); + this.tokenValue = tokenValue; + this.loginType = loginType; + this.service = service; } } 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 81f2dc87..a39331a0 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 @@ -247,6 +247,18 @@ public class StpLogic { // 5. 返回 return tokenValue; } + + /** + * 获取当前上下文的 TokenValue(如果获取不到则抛出异常) + * @return / + */ + public String getTokenValueNotNull(){ + String tokenValue = getTokenValue(); + if(SaFoxUtil.isEmpty(tokenValue)) { + throw new SaTokenException("未能读取到有效Token"); + } + return tokenValue; + } /** * 获取当前会话的Token信息 @@ -1676,7 +1688,7 @@ public class StpLogic { * @param at 注解对象 */ public void checkByAnnotation(SaCheckSafe at) { - this.checkSafe(); + this.checkSafe(at.value()); } /** @@ -1993,8 +2005,19 @@ public class StpLogic { * @param safeTime 维持时间 (单位: 秒) */ public void openSafe(long safeTime) { - long eff = System.currentTimeMillis() + safeTime * 1000; - getTokenSession().set(SaTokenConsts.SAFE_AUTH_SAVE_KEY, eff); + openSafe(SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE, safeTime); + } + + /** + * 在当前会话 开启二级认证 + * @param service 业务标识 + * @param safeTime 维持时间 (单位: 秒) + */ + public void openSafe(String service, long safeTime) { + // 开启二级认证前必须处于登录状态 + checkLogin(); + // 写入key + getSaTokenDao().set(splicingKeySafe(getTokenValueNotNull(), service), SaTokenConsts.SAFE_AUTH_SAVE_VALUE, safeTime); } /** @@ -2002,19 +2025,50 @@ public class StpLogic { * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时 */ public boolean isSafe() { - long eff = getTokenSession().get(SaTokenConsts.SAFE_AUTH_SAVE_KEY, 0L); - if(eff == 0 || eff < System.currentTimeMillis()) { + return isSafe(SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE); + } + + /** + * 当前会话 是否处于二级认证时间内 + * @param service 业务标识 + * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时 + */ + public boolean isSafe(String service) { + return isSafe(getTokenValue(), service); + } + + /** + * 指定 Token 是否处于二级认证时间内 + * @param tokenValue Token 值 + * @param service 业务标识 + * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时 + */ + public boolean isSafe(String tokenValue, String service) { + // 如果 Token 为空,则直接视为未认证 + if(SaFoxUtil.isEmpty(tokenValue)) { return false; } - return true; + + // 如果DB中可以查询出指定的键值,则代表已认证,否则视为未认证 + String value = getSaTokenDao().get(splicingKeySafe(tokenValue, service)); + return !(SaFoxUtil.isEmpty(value)); } /** * 检查当前会话是否已通过二级认证,如未通过则抛出异常 */ public void checkSafe() { - if (isSafe() == false) { - throw new NotSafeException(); + checkSafe(SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE); + } + + /** + * 检查当前会话是否已通过二级认证,如未通过则抛出异常 + * @param service 业务标识 + */ + public void checkSafe(String service) { + String tokenValue = getTokenValue(); + if (isSafe(tokenValue, service) == false) { + throw new NotSafeException(loginType, tokenValue, service); } } @@ -2023,18 +2077,45 @@ public class StpLogic { * @return 剩余有效时间 */ public long getSafeTime() { - long eff = getTokenSession().get(SaTokenConsts.SAFE_AUTH_SAVE_KEY, 0L); - if(eff == 0 || eff < System.currentTimeMillis()) { + return getSafeTime(SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE); + } + + /** + * 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证) + * @param service 业务标识 + * @return 剩余有效时间 + */ + public long getSafeTime(String service) { + // 如果上下文中没有 Token,则直接视为未认证 + String tokenValue = getTokenValue(); + if(SaFoxUtil.isEmpty(tokenValue)) { return SaTokenDao.NOT_VALUE_EXPIRE; } - return (eff - System.currentTimeMillis()) / 1000; + + // 从DB中查询这个key的剩余有效期 + return getSaTokenDao().getTimeout(splicingKeySafe(tokenValue, service)); } /** * 在当前会话 结束二级认证 */ public void closeSafe() { - getTokenSession().delete(SaTokenConsts.SAFE_AUTH_SAVE_KEY); + closeSafe(SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE); + } + + /** + * 在当前会话 结束二级认证 + * @param service 业务标识 + */ + public void closeSafe(String service) { + // 如果上下文中没有 Token,则无需任何操作 + String tokenValue = getTokenValue(); + if(SaFoxUtil.isEmpty(tokenValue)) { + return; + } + + // 删除 key + getSaTokenDao().delete(splicingKeySafe(tokenValue, service)); } @@ -2111,6 +2192,18 @@ public class StpLogic { return getConfig().getTokenName() + ":" + loginType + ":disable:" + service + ":" + loginId; } + /** + * 拼接key: 二级认证 + * @param tokenValue 要认证的 Token + * @param service 要认证的业务标识 + * @return key + */ + public String splicingKeySafe(String tokenValue, String service) { + // 格式::<账号类型>::<业务标识>: + // 形如:satoken:login:safe:important:gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__ + return getConfig().getTokenName() + ":" + loginType + ":safe:" + service + ":" + tokenValue; + } + // ------------------- Bean对象代理 ------------------- 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 931d7c3a..60b0b4b2 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 @@ -1002,6 +1002,15 @@ public class StpUtil { stpLogic.openSafe(safeTime); } + /** + * 在当前会话 开启二级认证 + * @param service 业务标识 + * @param safeTime 维持时间 (单位: 秒) + */ + public static void openSafe(String service, long safeTime) { + stpLogic.openSafe(service, safeTime); + } + /** * 当前会话 是否处于二级认证时间内 * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时 @@ -1010,12 +1019,39 @@ public class StpUtil { return stpLogic.isSafe(); } + /** + * 当前会话 是否处于二级认证时间内 + * @param service 业务标识 + * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时 + */ + public static boolean isSafe(String service) { + return stpLogic.isSafe(service); + } + + /** + * 指定 Token 是否处于二级认证时间内 + * @param tokenValue Token 值 + * @param service 业务标识 + * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时 + */ + public static boolean isSafe(String tokenValue, String service) { + return stpLogic.isSafe(tokenValue, service); + } + /** * 检查当前会话是否已通过二级认证,如未通过则抛出异常 */ public static void checkSafe() { stpLogic.checkSafe(); } + + /** + * 检查当前会话是否已通过二级认证,如未通过则抛出异常 + * @param service 业务标识 + */ + public static void checkSafe(String service) { + stpLogic.checkSafe(service); + } /** * 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证) @@ -1025,6 +1061,15 @@ public class StpUtil { return stpLogic.getSafeTime(); } + /** + * 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证) + * @param service 业务标识 + * @return 剩余有效时间 + */ + public static long getSafeTime(String service) { + return stpLogic.getSafeTime(service); + } + /** * 在当前会话 结束二级认证 */ @@ -1032,4 +1077,12 @@ public class StpUtil { stpLogic.closeSafe(); } + /** + * 在当前会话 结束二级认证 + * @param service 业务标识 + */ + public static void closeSafe(String service) { + stpLogic.closeSafe(service); + } + } 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 c90f77f1..16109947 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 @@ -80,10 +80,21 @@ public class SaTokenConsts { public static final String SWITCH_TO_SAVE_KEY = "SWITCH_TO_SAVE_KEY_"; /** - * 常量key标记: 在进行Token二级验证时使用的key + * 常量key标记: 在进行Token二级验证时,使用的key */ + @Deprecated public static final String SAFE_AUTH_SAVE_KEY = "SAFE_AUTH_SAVE_KEY_"; + /** + * 常量key标记: 在进行 Token 二级验证时,写入的 value 值 + */ + public static final String SAFE_AUTH_SAVE_VALUE = "SAFE_AUTH_SAVE_VALUE"; + + /** + * 常量key标记: 在进行 Token 二级验证时,默认的业务类型 + */ + public static final String DEFAULT_SAFE_AUTH_SERVICE = "important"; + // =================== token-style 相关 =================== diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/SafeAuthController.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/SafeAuthController.java index 441bed44..e8bad9b0 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/SafeAuthController.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/SafeAuthController.java @@ -23,7 +23,7 @@ public class SafeAuthController { * 测试步骤: 1、前端调用 deleteProject 接口,尝试删除仓库。 ---- http://localhost:8081/safe/deleteProject 2、后端校验会话尚未完成二级认证,返回: 仓库删除失败,请完成二级认证后再次访问接口。 - 3、前端将信息提示给用户,用户输入密码,调用 openSafe 接口。 ---- http://localhost:8081/safe/openSafe + 3、前端将信息提示给用户,用户输入密码,调用 openSafe 接口。 ---- http://localhost:8081/safe/openSafe?password=123456 4、后端比对用户输入的密码,完成二级认证,有效期为:120秒。 5、前端在 120 秒内再次调用 deleteProject 接口,尝试删除仓库。 ---- http://localhost:8081/safe/deleteProject 6、后端校验会话已完成二级认证,返回:仓库删除成功。 @@ -46,13 +46,13 @@ public class SafeAuthController { return SaResult.ok("仓库删除成功"); } - // 提供密码进行二级认证 ---- http://localhost:8081/safe/openSafe + // 提供密码进行二级认证 ---- http://localhost:8081/safe/openSafe?password=123456 @RequestMapping("openSafe") public SaResult openSafe(String password) { // 比对密码(此处只是举例,真实项目时可拿其它参数进行校验) if("123456".equals(password)) { - // 比对成功,为当前会话打开二级认证,有效期为120秒 + // 比对成功,为当前会话打开二级认证,有效期为120秒,意为在120秒内再调用 deleteProject 接口都无需提供密码 StpUtil.openSafe(120); return SaResult.ok("二级认证成功"); } @@ -61,4 +61,40 @@ public class SafeAuthController { return SaResult.error("二级认证失败"); } + + // ------------------ 指定业务类型进行二级认证 + + // 获取应用秘钥 ---- http://localhost:8081/safe/getClientSecret + @RequestMapping("getClientSecret") + public SaResult getClientSecret() { + // 第1步,先检查当前会话是否已完成 client业务 的二级认证 + StpUtil.checkSafe("client"); + + // 第2步,如果已完成二级认证,则返回数据 + return SaResult.data("aaaa-bbbb-cccc-dddd-eeee"); + } + + // 提供手势密码进行二级认证 ---- http://localhost:8081/safe/openClientSafe?gesture=35789 + @RequestMapping("openClientSafe") + public SaResult openClientSafe(String gesture) { + // 比对手势密码(此处只是举例,真实项目时可拿其它参数进行校验) + if("35789".equals(gesture)) { + + // 比对成功,为当前会话打开二级认证: + // 业务类型为:client + // 有效期为600秒==10分钟,意为在10分钟内,调用 getClientSecret 时都无需再提供手势密码 + StpUtil.openSafe("client", 600); + return SaResult.ok("二级认证成功"); + } + + // 如果密码校验失败,则二级认证也会失败 + return SaResult.error("二级认证失败"); + } + + // 查询当前会话是否已完成指定的二级认证 ---- http://localhost:8081/safe/isClientSafe + @RequestMapping("isClientSafe") + public SaResult isClientSafe() { + return SaResult.ok("当前是否已完成 client 二级认证:" + StpUtil.isSafe("client")); + } + } diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/current/GlobalException.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/current/GlobalException.java index 5ab4b887..fd1f2697 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/current/GlobalException.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/current/GlobalException.java @@ -46,7 +46,7 @@ public class GlobalException { @ExceptionHandler(NotSafeException.class) public SaResult handlerException(NotSafeException e) { e.printStackTrace(); - return SaResult.error("二级认证校验失败"); + return SaResult.error("二级认证校验失败:" + e.getService()); } // 拦截:服务封禁异常