diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaCheckOr.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaCheckOr.java index a2d2c716..f781da5e 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaCheckOr.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaCheckOr.java @@ -15,10 +15,7 @@ */ package cn.dev33.satoken.annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; /** * 批量注解鉴权:只要满足其中一个注解即可通过验证 @@ -88,4 +85,11 @@ public @interface SaCheckOr { */ SaCheckApiKey[] apikey() default {}; + /** + * 需要追加抓取的注解 Class (只能填写 Sa-Token 相关注解类型) + * + * @return / + */ + Class[] append() default {}; + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationHandlerInterface.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationHandlerInterface.java index fd91d396..995cb431 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationHandlerInterface.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationHandlerInterface.java @@ -16,7 +16,7 @@ package cn.dev33.satoken.annotation.handler; import java.lang.annotation.Annotation; -import java.lang.reflect.Method; +import java.lang.reflect.AnnotatedElement; /** * 所有注解处理器的父接口 @@ -35,18 +35,18 @@ public interface SaAnnotationHandlerInterface { /** * 所需要执行的校验方法 * @param at 注解对象 - * @param method 被标注的注解的方法引用 + * @param element 被标注的注解的元素(方法/类)引用 */ @SuppressWarnings("unchecked") - default void check(Annotation at, Method method) { - checkMethod((T) at, method); + default void check(Annotation at, AnnotatedElement element) { + checkMethod((T) at, element); } /** * 所需要执行的校验方法(转换类型后) * @param at 注解对象 - * @param method 被标注的注解的方法引用 + * @param element 被标注的注解的元素(方法/类)引用 */ - void checkMethod(T at, Method method); + void checkMethod(T at, AnnotatedElement element); } \ No newline at end of file diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckApiKeyHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckApiKeyHandler.java index b4587821..c8d6e2a3 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckApiKeyHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckApiKeyHandler.java @@ -20,7 +20,7 @@ import cn.dev33.satoken.annotation.SaMode; import cn.dev33.satoken.apikey.SaApiKeyUtil; import cn.dev33.satoken.context.SaHolder; -import java.lang.reflect.Method; +import java.lang.reflect.AnnotatedElement; /** * 注解 SaCheckApiKey 的处理器 @@ -36,7 +36,7 @@ public class SaCheckApiKeyHandler implements SaAnnotationHandlerInterface } @Override - public void checkMethod(SaCheckOr at, Method method) { - _checkMethod(at.login(), at.role(), at.permission(), at.safe(), at.httpBasic(), at.httpDigest(), at.disable(), at.apikey(), method); + public void checkMethod(SaCheckOr at, AnnotatedElement element) { + _checkMethod(at.login(), at.role(), at.permission(), at.safe(), at.httpBasic(), at.httpDigest(), at.disable(), at.apikey(), at.append(), element); } public static void _checkMethod( @@ -52,7 +52,8 @@ public class SaCheckOrHandler implements SaAnnotationHandlerInterface SaCheckHttpDigest[] httpDigest, SaCheckDisable[] disable, SaCheckApiKey[] apikey, - Method method + Class[] append, + AnnotatedElement element ) { // 先把所有注解塞到一个 list 里 List annotationList = new ArrayList<>(); @@ -64,6 +65,12 @@ public class SaCheckOrHandler implements SaAnnotationHandlerInterface annotationList.addAll(Arrays.asList(httpBasic)); annotationList.addAll(Arrays.asList(httpDigest)); annotationList.addAll(Arrays.asList(apikey)); + for (Class annotationClass : append) { + Annotation annotation = SaAnnotationStrategy.instance.getAnnotation.apply(element, annotationClass); + if(annotation != null) { + annotationList.add(annotation); + } + } // 如果 atList 为空,说明 SaCheckOr 上不包含任何注解校验,我们直接跳过即可 if(annotationList.isEmpty()) { @@ -74,7 +81,7 @@ public class SaCheckOrHandler implements SaAnnotationHandlerInterface List errorList = new ArrayList<>(); for (Annotation item : annotationList) { try { - SaAnnotationStrategy.instance.annotationHandlerMap.get(item.annotationType()).check(item, method); + SaAnnotationStrategy.instance.annotationHandlerMap.get(item.annotationType()).check(item, element); // 只要有一个校验通过,就可以直接返回了 return; } catch (SaTokenException e) { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckPermissionHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckPermissionHandler.java index 62321098..ca423275 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckPermissionHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckPermissionHandler.java @@ -22,7 +22,7 @@ import cn.dev33.satoken.exception.NotPermissionException; import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.util.SaFoxUtil; -import java.lang.reflect.Method; +import java.lang.reflect.AnnotatedElement; /** * 注解 SaCheckPermission 的处理器 @@ -38,7 +38,7 @@ public class SaCheckPermissionHandler implements SaAnnotationHandlerInterface v1.43.0 版本起,SaIgnore 注解处理逻辑已转移到全局策略中,此处理器代码仅做留档 * * @author click33 * @since 2024/8/2 @@ -34,7 +35,7 @@ public class SaIgnoreHandler implements SaAnnotationHandlerInterface { } @Override - public void checkMethod(SaIgnore at, Method method) { + public void checkMethod(SaIgnore at, AnnotatedElement element) { _checkMethod(); } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/secure/totp/SaTotpTemplate.java b/sa-token-core/src/main/java/cn/dev33/satoken/secure/totp/SaTotpTemplate.java index 34c34089..04a2cd3d 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/secure/totp/SaTotpTemplate.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/secure/totp/SaTotpTemplate.java @@ -155,8 +155,8 @@ public class SaTotpTemplate { * 生成谷歌认证器的扫码字符串 (形如:otpauth://totp/{issuer}:{account}?secret={secretKey}&issuer={issuer}) * * @param account 账户名 - * @param secretKey TOTP 秘钥 * @param issuer 签发者 + * @param secretKey TOTP 秘钥 * @return / */ public String generateGoogleSecretKey(String account, String issuer, String secretKey) { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/secure/totp/SaTotpUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/secure/totp/SaTotpUtil.java index 915d86a2..4471f5fc 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/secure/totp/SaTotpUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/secure/totp/SaTotpUtil.java @@ -88,4 +88,16 @@ public class SaTotpUtil { return SaManager.getSaTotpTemplate().generateGoogleSecretKey(account, secretKey); } + /** + * 生成谷歌认证器的扫码字符串 (形如:otpauth://totp/{issuer}:{account}?secret={secretKey}&issuer={issuer}) + * + * @param account 账户名 + * @param issuer 签发者 + * @param secretKey TOTP 秘钥 + * @return / + */ + public static String generateGoogleSecretKey(String account, String issuer, String secretKey) { + return SaManager.getSaTotpTemplate().generateGoogleSecretKey(account, issuer, secretKey); + } + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaAnnotationStrategy.java b/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaAnnotationStrategy.java index a53baa33..d326e719 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaAnnotationStrategy.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaAnnotationStrategy.java @@ -17,15 +17,12 @@ package cn.dev33.satoken.strategy; import cn.dev33.satoken.annotation.*; import cn.dev33.satoken.annotation.handler.*; -import cn.dev33.satoken.fun.strategy.SaCheckELRootMapExtendFunction; -import cn.dev33.satoken.fun.strategy.SaCheckMethodAnnotationFunction; -import cn.dev33.satoken.fun.strategy.SaGetAnnotationFunction; -import cn.dev33.satoken.fun.strategy.SaIsAnnotationPresentFunction; +import cn.dev33.satoken.fun.strategy.*; import cn.dev33.satoken.listener.SaTokenEventCenter; +import cn.dev33.satoken.router.SaRouter; import java.lang.annotation.Annotation; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; /** * Sa-Token 注解鉴权相关策略 @@ -56,7 +53,6 @@ public final class SaAnnotationStrategy { * 注册所有默认的注解处理器 */ public void registerDefaultAnnotationHandler() { - annotationHandlerMap.put(SaIgnore.class, new SaIgnoreHandler()); annotationHandlerMap.put(SaCheckLogin.class, new SaCheckLoginHandler()); annotationHandlerMap.put(SaCheckRole.class, new SaCheckRoleHandler()); annotationHandlerMap.put(SaCheckPermission.class, new SaCheckPermissionHandler()); @@ -98,21 +94,42 @@ public final class SaAnnotationStrategy { /** * 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现) */ - @SuppressWarnings("unchecked") public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> { - // 遍历所有的注解处理器,检查此 method 是否具有这些指定的注解 + + // 如果 Method 或其所属 Class 上有 @SaIgnore 注解,则直接跳过整个校验过程 + if(instance.isAnnotationPresent.apply(method, SaIgnore.class)) { + SaRouter.stop(); + } + + // 先校验 Method 所属 Class 上的注解 + instance.checkElementAnnotation.accept(method.getDeclaringClass()); + + // 再校验 Method 上的注解 + instance.checkElementAnnotation.accept(method); + }; + + /** + * 对一个 [Element] 对象进行注解校验 (注解鉴权内部实现) + */ + @SuppressWarnings("unchecked") + public SaCheckElementAnnotationFunction checkElementAnnotation = (element) -> { + // 如果此元素上标注了 @SaCheckOr,则必须在后续判断中忽略掉其指定的 append() 类型注解判断 + List> ignoreClassList = new ArrayList<>(); + SaCheckOr checkOr = (SaCheckOr)instance.getAnnotation.apply(element, SaCheckOr.class); + if(checkOr != null) { + ignoreClassList = Arrays.asList(checkOr.append()); + } + + // 遍历所有的注解处理器,检查此 element 是否具有这些指定的注解 for (Map.Entry, SaAnnotationHandlerInterface> entry: annotationHandlerMap.entrySet()) { - - // 先校验 Method 所属 Class 上的注解 - Annotation classTakeAnnotation = instance.getAnnotation.apply(method.getDeclaringClass(), (Class)entry.getKey()); - if(classTakeAnnotation != null) { - entry.getValue().check(classTakeAnnotation, method); + // 忽略掉在 @SaCheckOr 中 append 字段指定的注解 + Class atClass = (Class)entry.getKey(); + if(ignoreClassList.contains(atClass)) { + continue; } - - // 再校验 Method 上的注解 - Annotation methodTakeAnnotation = instance.getAnnotation.apply(method, (Class)entry.getKey()); - if(methodTakeAnnotation != null) { - entry.getValue().check(methodTakeAnnotation, method); + Annotation annotation = instance.getAnnotation.apply(element, atClass); + if(annotation != null) { + entry.getValue().check(annotation, element); } } }; @@ -121,7 +138,6 @@ public final class SaAnnotationStrategy { * 从元素上获取注解 */ public SaGetAnnotationFunction getAnnotation = (element, annotationClass)->{ - // 默认使用jdk的注解处理器 return element.getAnnotation(annotationClass); }; diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java index 2b49badf..8cb02203 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java @@ -6,7 +6,7 @@ import cn.dev33.satoken.exception.SaTokenException; import com.pj.satoken.custom_annotation.CheckAccount; import org.springframework.stereotype.Component; -import java.lang.reflect.Method; +import java.lang.reflect.AnnotatedElement; /** * 注解 CheckAccount 的处理器 @@ -25,7 +25,7 @@ public class CheckAccountHandler implements SaAnnotationHandlerInterface