二级认证新增指定业务标识功能

This commit is contained in:
click33 2022-10-23 04:44:39 +08:00
parent 05b227e332
commit 432bfe36bf
7 changed files with 273 additions and 23 deletions

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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) {
// 格式<Token名称>:<账号类型>:<safe>:<业务标识>:<Token值>
// 形如satoken:login:safe:important:gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__
return getConfig().getTokenName() + ":" + loginType + ":safe:" + service + ":" + tokenValue;
}
// ------------------- Bean对象代理 -------------------

View File

@ -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);
}
}

View File

@ -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 相关 ===================

View File

@ -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"));
}
}

View File

@ -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());
}
// 拦截服务封禁异常