From cd0c20793a577cdcdbf91127355f9bfda92b367d Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 3 Aug 2024 19:08:51 +0800 Subject: [PATCH] =?UTF-8?q?SaAnnotationAbstractHandler=20=E7=94=B1?= =?UTF-8?q?=E6=8A=BD=E8=B1=A1=E7=B1=BB=E6=94=B9=E4=B8=BA=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/SaAnnotationAbstractHandler.java | 52 +++++ .../handler/SaCheckDisableHandler.java | 51 +++++ .../handler/SaCheckHttpBasicHandler.java | 45 ++++ .../handler/SaCheckHttpDigestHandler.java | 64 ++++++ .../handler/SaCheckLoginHandler.java | 48 +++++ .../annotation/handler/SaCheckOrHandler.java | 87 ++++++++ .../handler/SaCheckPermissionHandler.java | 68 ++++++ .../handler/SaCheckRoleHandler.java | 54 +++++ .../handler/SaCheckSafeHandler.java | 48 +++++ .../annotation/handler/SaIgnoreHandler.java | 45 ++++ .../strategy/SaAnnotationStrategy.java | 124 +++++++++++ .../com/pj/cases/test/TestController.java | 2 + .../custom_annotation/CheckAccount.java | 33 +++ .../custom_annotation/SaUserCheckSafe.java | 28 +++ .../handler/CheckAccountHandler.java | 42 ++++ .../handler/SaUserCheckLoginHandler.java | 29 +++ .../handler/SaUserCheckPermissionHandler.java | 29 +++ .../handler/SaUserCheckRoleHandler.java | 29 +++ .../handler/SaUserCheckSafeHandler.java | 29 +++ .../merge_annotation/SaUserCheckLogin.java | 22 ++ .../SaUserCheckPermission.java | 56 +++++ .../merge_annotation/SaUserCheckRole.java | 38 ++++ .../merge_annotation/SaUserCheckSafe.java | 31 +++ sa-token-doc/fun/custom-annotations.md | 199 ++++++++++++++++++ 24 files changed, 1253 insertions(+) create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationAbstractHandler.java create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckDisableHandler.java create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpBasicHandler.java create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpDigestHandler.java create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckLoginHandler.java create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckOrHandler.java create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckPermissionHandler.java create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckRoleHandler.java create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckSafeHandler.java create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaIgnoreHandler.java create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaAnnotationStrategy.java create mode 100644 sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/CheckAccount.java create mode 100644 sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/SaUserCheckSafe.java create mode 100644 sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java create mode 100644 sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckLoginHandler.java create mode 100644 sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckPermissionHandler.java create mode 100644 sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckRoleHandler.java create mode 100644 sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckSafeHandler.java create mode 100644 sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckLogin.java create mode 100644 sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckPermission.java create mode 100644 sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckRole.java create mode 100644 sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckSafe.java create mode 100644 sa-token-doc/fun/custom-annotations.md diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationAbstractHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationAbstractHandler.java new file mode 100644 index 00000000..b9d8b4ba --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationAbstractHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.annotation.handler; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; + +/** + * 所有注解处理器的父接口 + * + * @author click33 + * @since 2024/8/2 + */ +public interface SaAnnotationAbstractHandler { + + /** + * 获取所要处理的注解类型 + * @return / + */ + Class getHandlerAnnotationClass(); + + /** + * 所需要执行的校验方法 + * @param at 注解对象 + * @param element 被标注的注解的元素引用(类或方法) + */ + @SuppressWarnings("unchecked") + default void check(Annotation at, AnnotatedElement element) { + checkMethod((T) at, element); + } + + /** + * 所需要执行的校验方法(转换类型后) + * @param at 注解对象 + * @param element 被标注的注解的元素引用(类或方法) + */ + 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/SaCheckDisableHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckDisableHandler.java new file mode 100644 index 00000000..4f613612 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckDisableHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.annotation.handler; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.annotation.SaCheckDisable; +import cn.dev33.satoken.stp.StpLogic; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaCheckDisable 的处理器 + * + * @author click33 + * @since 2024/8/2 + */ +public class SaCheckDisableHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckDisable.class; + } + + @Override + public void checkMethod(SaCheckDisable at, AnnotatedElement element) { + _checkMethod(at.type(), at.value(), at.level()); + } + + public static void _checkMethod(String type, String[] value, int level) { + StpLogic stpLogic = SaManager.getStpLogic(type, false); + + Object loginId = stpLogic.getLoginId(); + for (String service : value) { + stpLogic.checkDisableLevel(loginId, service, level); + } + } + +} \ No newline at end of file diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpBasicHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpBasicHandler.java new file mode 100644 index 00000000..52734d50 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpBasicHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.annotation.handler; + +import cn.dev33.satoken.annotation.SaCheckHttpBasic; +import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaCheckHttpBasic 的处理器 + * + * @author click33 + * @since 2024/8/2 + */ +public class SaCheckHttpBasicHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckHttpBasic.class; + } + + @Override + public void checkMethod(SaCheckHttpBasic at, AnnotatedElement element) { + _checkMethod(at.realm(), at.account()); + } + + public static void _checkMethod(String realm, String account) { + SaHttpBasicUtil.check(realm, account); + } + +} \ No newline at end of file diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpDigestHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpDigestHandler.java new file mode 100644 index 00000000..58d7971f --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpDigestHandler.java @@ -0,0 +1,64 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.annotation.handler; + +import cn.dev33.satoken.annotation.SaCheckHttpDigest; +import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.httpauth.digest.SaHttpDigestUtil; +import cn.dev33.satoken.util.SaFoxUtil; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaCheckHttpDigest 的处理器 + * + * @author click33 + * @since 2024/8/2 + */ +public class SaCheckHttpDigestHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckHttpDigest.class; + } + + @Override + public void checkMethod(SaCheckHttpDigest at, AnnotatedElement element) { + _checkMethod(at.username(), at.password(), at.realm(), at.value()); + } + + public static void _checkMethod(String username, String password, String realm, String value) { + // 如果配置了 value,则以 value 优先 + if(SaFoxUtil.isNotEmpty(value)){ + String[] arr = value.split(":"); + if(arr.length != 2){ + throw new SaTokenException("注解参数配置错误,格式应如:username:password"); + } + SaHttpDigestUtil.check(arr[0], arr[1]); + return; + } + + // 如果配置了 username,则分别获取参数 + if(SaFoxUtil.isNotEmpty(username)){ + SaHttpDigestUtil.check(username, password, realm); + return; + } + + // 都没有配置,则根据全局配置参数进行校验 + SaHttpDigestUtil.check(); + } + +} \ No newline at end of file diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckLoginHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckLoginHandler.java new file mode 100644 index 00000000..feb4dc33 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckLoginHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.annotation.handler; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.annotation.SaCheckLogin; +import cn.dev33.satoken.stp.StpLogic; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaCheckLogin 的处理器 + * + * @author click33 + * @since 2024/8/2 + */ +public class SaCheckLoginHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckLogin.class; + } + + @Override + public void checkMethod(SaCheckLogin at, AnnotatedElement element) { + _checkMethod(at.type()); + } + + public static void _checkMethod(String type) { + StpLogic stpLogic = SaManager.getStpLogic(type, false); + + stpLogic.checkLogin(); + } + +} \ No newline at end of file diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckOrHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckOrHandler.java new file mode 100644 index 00000000..8448b110 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckOrHandler.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.annotation.handler; + +import cn.dev33.satoken.annotation.*; +import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.strategy.SaAnnotationStrategy; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 注解 SaCheckOr 的处理器 + * + * @author click33 + * @since 2024/8/2 + */ +public class SaCheckOrHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckOr.class; + } + + @Override + public void checkMethod(SaCheckOr at, AnnotatedElement element) { + _checkMethod(at.login(), at.role(), at.permission(), at.safe(), at.httpBasic(), at.httpDigest(), at.disable(), element); + } + + public static void _checkMethod( + SaCheckLogin[] login, + SaCheckRole[] role, + SaCheckPermission[] permission, + SaCheckSafe[] safe, + SaCheckHttpBasic[] httpBasic, + SaCheckHttpDigest[] httpDigest, + SaCheckDisable[] disable, + AnnotatedElement element + ) { + // 先把所有注解塞到一个 list 里 + List annotationList = new ArrayList<>(); + annotationList.addAll(Arrays.asList(login)); + annotationList.addAll(Arrays.asList(role)); + annotationList.addAll(Arrays.asList(permission)); + annotationList.addAll(Arrays.asList(safe)); + annotationList.addAll(Arrays.asList(disable)); + annotationList.addAll(Arrays.asList(httpBasic)); + annotationList.addAll(Arrays.asList(httpDigest)); + + // 如果 atList 为空,说明 SaCheckOr 上不包含任何注解校验,我们直接跳过即可 + if(annotationList.isEmpty()) { + return; + } + + // 逐个开始校验 >>> + List errorList = new ArrayList<>(); + for (Annotation item : annotationList) { + try { + SaAnnotationStrategy.instance.annotationHandlerMap.get(item.annotationType()).check(item, element); + // 只要有一个校验通过,就可以直接返回了 + return; + } catch (SaTokenException e) { + errorList.add(e); + } + } + + // 执行至此,说明所有注解校验都通过不了,此时 errorList 里面会有多个异常,我们随便抛出一个即可 + throw errorList.get(0); + } + +} \ No newline at end of file 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 new file mode 100644 index 00000000..b2e18b7e --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckPermissionHandler.java @@ -0,0 +1,68 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.annotation.handler; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; +import cn.dev33.satoken.exception.NotPermissionException; +import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.util.SaFoxUtil; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaCheckPermission 的处理器 + * + * @author click33 + * @since 2024/8/2 + */ +public class SaCheckPermissionHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckPermission.class; + } + + @Override + public void checkMethod(SaCheckPermission at, AnnotatedElement element) { + _checkMethod(at.type(), at.value(), at.mode(), at.orRole()); + } + + public static void _checkMethod(String type, String[] value, SaMode mode, String[] orRole) { + StpLogic stpLogic = SaManager.getStpLogic(type, false); + + String[] permissionArray = value; + try { + if(mode == SaMode.AND) { + stpLogic.checkPermissionAnd(permissionArray); + } else { + stpLogic.checkPermissionOr(permissionArray); + } + } catch (NotPermissionException e) { + // 权限认证校验未通过,再开始角色认证校验 + for (String role : orRole) { + String[] rArr = SaFoxUtil.convertStringToArray(role); + // 某一项 role 认证通过,则可以提前退出了,代表通过 + if (stpLogic.hasRoleAnd(rArr)) { + return; + } + } + throw e; + } + } + +} \ No newline at end of file diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckRoleHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckRoleHandler.java new file mode 100644 index 00000000..24787e30 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckRoleHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.annotation.handler; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.annotation.SaCheckRole; +import cn.dev33.satoken.annotation.SaMode; +import cn.dev33.satoken.stp.StpLogic; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaCheckRole 的处理器 + * + * @author click33 + * @since 2024/8/2 + */ +public class SaCheckRoleHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckRole.class; + } + + @Override + public void checkMethod(SaCheckRole at, AnnotatedElement element) { + _checkMethod(at.type(), at.value(), at.mode()); + } + + public static void _checkMethod(String type, String[] value, SaMode mode) { + StpLogic stpLogic = SaManager.getStpLogic(type, false); + + String[] roleArray = value; + if(mode == SaMode.AND) { + stpLogic.checkRoleAnd(roleArray); + } else { + stpLogic.checkRoleOr(roleArray); + } + } + +} \ No newline at end of file diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckSafeHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckSafeHandler.java new file mode 100644 index 00000000..8f6ca9b0 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckSafeHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.annotation.handler; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.annotation.SaCheckSafe; +import cn.dev33.satoken.stp.StpLogic; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaCheckSafe 的处理器 + * + * @author click33 + * @since 2024/8/2 + */ +public class SaCheckSafeHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckSafe.class; + } + + @Override + public void checkMethod(SaCheckSafe at, AnnotatedElement element) { + _checkMethod(at.type(), at.value()); + } + + public static void _checkMethod(String type, String value) { + StpLogic stpLogic = SaManager.getStpLogic(type, false); + + stpLogic.checkSafe(value); + } + +} \ No newline at end of file diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaIgnoreHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaIgnoreHandler.java new file mode 100644 index 00000000..cbc86067 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaIgnoreHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.annotation.handler; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.dev33.satoken.router.SaRouter; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaIgnore 的处理器 + * + * @author click33 + * @since 2024/8/2 + */ +public class SaIgnoreHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaIgnore.class; + } + + @Override + public void checkMethod(SaIgnore at, AnnotatedElement element) { + _checkMethod(); + } + + public static void _checkMethod() { + SaRouter.stop(); + } + +} \ No newline at end of file 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 new file mode 100644 index 00000000..12f24050 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaAnnotationStrategy.java @@ -0,0 +1,124 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.strategy; + +import cn.dev33.satoken.annotation.*; +import cn.dev33.satoken.annotation.handler.*; +import cn.dev33.satoken.fun.strategy.SaCheckMethodAnnotationFunction; +import cn.dev33.satoken.fun.strategy.SaGetAnnotationFunction; +import cn.dev33.satoken.listener.SaTokenEventCenter; + +import java.lang.annotation.Annotation; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Sa-Token 注解鉴权相关策略 + * + * @author click33 + * @since 1.39.0 + */ +public final class SaAnnotationStrategy { + + private SaAnnotationStrategy() { + registerDefaultAnnotationHandler(); + } + + /** + * 全局单例引用 + */ + public static final SaAnnotationStrategy instance = new SaAnnotationStrategy(); + + + // ----------------------- 所有策略 + + /** + * 注解处理器集合 + */ + public Map, SaAnnotationAbstractHandler> annotationHandlerMap = new LinkedHashMap<>(); + + /** + * 注册所有默认的注解处理器 + */ + 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()); + annotationHandlerMap.put(SaCheckSafe.class, new SaCheckSafeHandler()); + annotationHandlerMap.put(SaCheckDisable.class, new SaCheckDisableHandler()); + annotationHandlerMap.put(SaCheckHttpBasic.class, new SaCheckHttpBasicHandler()); + annotationHandlerMap.put(SaCheckHttpDigest.class, new SaCheckHttpDigestHandler()); + annotationHandlerMap.put(SaCheckOr.class, new SaCheckOrHandler()); + } + + /** + * 注册一个注解处理器 + */ + public void registerAnnotationHandler(SaAnnotationAbstractHandler handler) { + annotationHandlerMap.put(handler.getHandlerAnnotationClass(), handler); + SaTokenEventCenter.doRegisterAnnotationHandler(handler); + } + + /** + * 注册一个注解处理器,到首位 + */ + public void registerAnnotationHandlerToFirst(SaAnnotationAbstractHandler handler) { + Map, SaAnnotationAbstractHandler> newMap = new LinkedHashMap<>(); + newMap.put(handler.getHandlerAnnotationClass(), handler); + newMap.putAll(annotationHandlerMap); + this.annotationHandlerMap = newMap; + SaTokenEventCenter.doRegisterAnnotationHandler(handler); + } + + /** + * 移除一个注解处理器 + */ + public void removeAnnotationHandler(Class cls) { + annotationHandlerMap.remove(cls); + } + + /** + * 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现) + */ + @SuppressWarnings("unchecked") + public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> { + // 遍历所有的注解处理器,检查此 method 是否具有这些指定的注解 + for (Map.Entry, SaAnnotationAbstractHandler> entry: annotationHandlerMap.entrySet()) { + + // 先校验 Method 所属 Class 上的注解 + Annotation classTakeAnnotation = instance.getAnnotation.apply(method.getDeclaringClass(), (Class)entry.getKey()); + if(classTakeAnnotation != null) { + entry.getValue().check(classTakeAnnotation, method.getDeclaringClass()); + } + + // 再校验 Method 上的注解 + Annotation methodTakeAnnotation = instance.getAnnotation.apply(method, (Class)entry.getKey()); + if(methodTakeAnnotation != null) { + entry.getValue().check(methodTakeAnnotation, method); + } + } + }; + + /** + * 从元素上获取注解 + */ + 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/cases/test/TestController.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/test/TestController.java index da5aa8eb..a4ea6f76 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/test/TestController.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/test/TestController.java @@ -1,6 +1,7 @@ package com.pj.cases.test; import cn.dev33.satoken.util.SaResult; +import com.pj.satoken.custom_annotation.CheckAccount; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -15,6 +16,7 @@ public class TestController { // 测试 浏览器访问: http://localhost:8081/test/test @RequestMapping("test") + @CheckAccount(name = "sa", pwd = "123456") public SaResult test() { System.out.println("------------进来了"); return SaResult.ok(); diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/CheckAccount.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/CheckAccount.java new file mode 100644 index 00000000..2c01a5ae --- /dev/null +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/CheckAccount.java @@ -0,0 +1,33 @@ +package com.pj.satoken.custom_annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 账号校验:在标注一个方法上时,要求前端必须提交相应的账号密码参数才能访问方法。 + * + * @author click33 + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE}) +public @interface CheckAccount { + + /** + * 需要校验的账号 + * + * @return / + */ + String name(); + + /** + * 需要校验的密码 + * + * @return / + */ + String pwd(); + + +} diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/SaUserCheckSafe.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/SaUserCheckSafe.java new file mode 100644 index 00000000..49e2da67 --- /dev/null +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/SaUserCheckSafe.java @@ -0,0 +1,28 @@ +package com.pj.satoken.custom_annotation; + +import cn.dev33.satoken.util.SaTokenConsts; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 二级认证校验(User版):客户端必须完成二级认证之后,才能进入该方法,否则将被抛出异常。 + * + *

可标注在方法、类上(效果等同于标注在此类的所有方法上)。 + * + * @author click33 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface SaUserCheckSafe { + + /** + * 要校验的服务 + * + * @return / + */ + String value() default SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE; + +} 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 new file mode 100644 index 00000000..5d5e4a60 --- /dev/null +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java @@ -0,0 +1,42 @@ +package com.pj.satoken.custom_annotation.handler; + +import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.exception.SaTokenException; +import com.pj.satoken.custom_annotation.CheckAccount; +import org.springframework.stereotype.Component; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 CheckAccount 的处理器 + * + * @author click33 + * + */ +@Component +public class CheckAccountHandler implements SaAnnotationAbstractHandler { + + // 指定这个处理器要处理哪个注解 + @Override + public Class getHandlerAnnotationClass() { + return CheckAccount.class; + } + + // 每次请求校验注解时,会执行的方法 + @Override + public void checkMethod(CheckAccount at, AnnotatedElement element) { + // 获取前端请求提交的参数 + String name = SaHolder.getRequest().getParamNotNull("name"); + String pwd = SaHolder.getRequest().getParamNotNull("pwd"); + + // 与注解中指定的值相比较 + if(name.equals(at.name()) && pwd.equals(at.pwd()) ) { + // 校验通过,什么也不做 + } else { + // 校验不通过,则抛出异常 + throw new SaTokenException("账号或密码错误,未通过校验"); + } + } + +} diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckLoginHandler.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckLoginHandler.java new file mode 100644 index 00000000..31f0b139 --- /dev/null +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckLoginHandler.java @@ -0,0 +1,29 @@ +package com.pj.satoken.custom_annotation.handler; + +import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaCheckLoginHandler; +import com.pj.satoken.StpUserUtil; +import com.pj.satoken.custom_annotation.SaUserCheckLogin; +import org.springframework.stereotype.Component; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaUserCheckLogin 的处理器 + * + * @author click33 + */ +@Component +public class SaUserCheckLoginHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaUserCheckLogin.class; + } + + @Override + public void checkMethod(SaUserCheckLogin at, AnnotatedElement element) { + SaCheckLoginHandler._checkMethod(StpUserUtil.TYPE); + } + +} diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckPermissionHandler.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckPermissionHandler.java new file mode 100644 index 00000000..6b1f1d8f --- /dev/null +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckPermissionHandler.java @@ -0,0 +1,29 @@ +package com.pj.satoken.custom_annotation.handler; + +import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaCheckPermissionHandler; +import com.pj.satoken.StpUserUtil; +import com.pj.satoken.custom_annotation.SaUserCheckPermission; +import org.springframework.stereotype.Component; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaUserCheckPermission 的处理器 + * + * @author click33 + */ +@Component +public class SaUserCheckPermissionHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaUserCheckPermission.class; + } + + @Override + public void checkMethod(SaUserCheckPermission at, AnnotatedElement element) { + SaCheckPermissionHandler._checkMethod(StpUserUtil.TYPE, at.value(), at.mode(), at.orRole()); + } + +} diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckRoleHandler.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckRoleHandler.java new file mode 100644 index 00000000..51cbbe76 --- /dev/null +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckRoleHandler.java @@ -0,0 +1,29 @@ +package com.pj.satoken.custom_annotation.handler; + +import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaCheckRoleHandler; +import com.pj.satoken.StpUserUtil; +import com.pj.satoken.custom_annotation.SaUserCheckRole; +import org.springframework.stereotype.Component; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaUserCheckRole 的处理器 + * + * @author click33 + */ +@Component +public class SaUserCheckRoleHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaUserCheckRole.class; + } + + @Override + public void checkMethod(SaUserCheckRole at, AnnotatedElement element) { + SaCheckRoleHandler._checkMethod(StpUserUtil.TYPE, at.value(), at.mode()); + } + +} diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckSafeHandler.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckSafeHandler.java new file mode 100644 index 00000000..9f78ccfc --- /dev/null +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckSafeHandler.java @@ -0,0 +1,29 @@ +package com.pj.satoken.custom_annotation.handler; + +import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaCheckSafeHandler; +import com.pj.satoken.StpUserUtil; +import com.pj.satoken.custom_annotation.SaUserCheckSafe; +import org.springframework.stereotype.Component; + +import java.lang.reflect.AnnotatedElement; + +/** + * 注解 SaUserCheckPermission 的处理器 + * + * @author click33 + */ +@Component +public class SaUserCheckSafeHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaUserCheckSafe.class; + } + + @Override + public void checkMethod(SaUserCheckSafe at, AnnotatedElement element) { + SaCheckSafeHandler._checkMethod(StpUserUtil.TYPE, at.value()); + } + +} diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckLogin.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckLogin.java new file mode 100644 index 00000000..45e60c5f --- /dev/null +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckLogin.java @@ -0,0 +1,22 @@ +package com.pj.satoken.merge_annotation; + +import cn.dev33.satoken.annotation.SaCheckLogin; +import com.pj.satoken.StpUserUtil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 登录认证(User版):只有登录之后才能进入该方法 + *

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

可标注在函数、类上(效果等同于标注在此类的所有方法上) + * @author click33 + * + */ +@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; + + /** + * 在权限校验不通过时的次要选择,两者只要其一校验成功即可通过校验 + * + *

+ * 例1:@SaCheckPermission(value="user-add", orRole="admin"), + * 代表本次请求只要具有 user-add权限 或 admin角色 其一即可通过校验。 + *

+ * + *

+ * 例2: orRole = {"admin", "manager", "staff"},具有三个角色其一即可。
+ * 例3: orRole = {"admin, manager, staff"},必须三个角色同时具备。 + *

+ * + * @return / + */ + @AliasFor(annotation = SaCheckPermission.class) + String[] orRole() default {}; + +} diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckRole.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckRole.java new file mode 100644 index 00000000..ff81b676 --- /dev/null +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckRole.java @@ -0,0 +1,38 @@ +package com.pj.satoken.merge_annotation; + +import cn.dev33.satoken.annotation.SaCheckRole; +import cn.dev33.satoken.annotation.SaMode; +import com.pj.satoken.StpUserUtil; +import org.springframework.core.annotation.AliasFor; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 角色认证(User版):必须具有指定角色标识才能进入该方法 + *

可标注在函数、类上(效果等同于标注在此类的所有方法上) + * @author click33 + * + */ +@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-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckSafe.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckSafe.java new file mode 100644 index 00000000..60bd4e56 --- /dev/null +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/merge_annotation/SaUserCheckSafe.java @@ -0,0 +1,31 @@ +package com.pj.satoken.merge_annotation; + +import cn.dev33.satoken.annotation.SaCheckSafe; +import cn.dev33.satoken.util.SaTokenConsts; +import com.pj.satoken.StpUserUtil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 二级认证校验(User版):客户端必须完成二级认证之后,才能进入该方法,否则将被抛出异常。 + * + *

可标注在方法、类上(效果等同于标注在此类的所有方法上)。 + * + * @author click33 + */ +@SaCheckSafe(type = StpUserUtil.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface SaUserCheckSafe { + + /** + * 要校验的服务 + * + * @return / + */ + String value() default SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE; + +} diff --git a/sa-token-doc/fun/custom-annotations.md b/sa-token-doc/fun/custom-annotations.md new file mode 100644 index 00000000..c992517d --- /dev/null +++ b/sa-token-doc/fun/custom-annotations.md @@ -0,0 +1,199 @@ +# 自定义注解 + +如果框架内置的注解无法满足你的业务需求,你还可以自定义注解注入到框架中。 + +--- + +### 1、自定义注解 + +假设有以下业务需求 + +> [!INFO| label:需求场景] +> 自定义一个注解 `@CheckAccount`,具有 `name`、`pwd` 两个字段,在标注一个方法上时,要求前端必须提交相应的账号密码参数才能访问方法。 + + +#### 1.1、第一步,创建一个注解 + +``` java +/** + * 账号校验:在标注一个方法上时,要求前端必须提交相应的账号密码参数才能访问方法。 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE}) +public @interface CheckAccount { + + /** + * 需要校验的账号 + */ + String name(); + + /** + * 需要校验的密码 + */ + String pwd(); + +} +``` + +#### 1.2、第二步,创建注解处理器 + +实现 `SaAnnotationAbstractHandler` 接口,指定泛型为刚才自定义的注解 + +``` java +/** + * 注解 CheckAccount 的处理器 + */ +@Component +public class CheckAccountHandler implements SaAnnotationAbstractHandler { + + // 指定这个处理器要处理哪个注解 + @Override + public Class getHandlerAnnotationClass() { + return CheckAccount.class; + } + + // 每次请求校验注解时,会执行的方法 + @Override + public void checkMethod(CheckAccount at, AnnotatedElement element) { + // 获取前端请求提交的参数 + String name = SaHolder.getRequest().getParamNotNull("name"); + String pwd = SaHolder.getRequest().getParamNotNull("pwd"); + + // 与注解中指定的值相比较 + if(name.equals(at.name()) && pwd.equals(at.pwd()) ) { + // 校验通过,什么也不做 + } else { + // 校验不通过,则抛出异常 + throw new SaTokenException("账号或密码错误,未通过校验"); + } + } + +} +``` + +参考上述代码,实现类上指定了 `@Component` 注解,使其可以在 ioc 环境下(如 Spring)被自动扫描注册 Sa-Token 中, +如果你的项目属于非 ioc 环境,则需要手动将其注册到 Sa-Token 框架中: +``` java +SaAnnotationStrategy.instance.registerAnnotationHandler(new CheckAccountHandler()); +``` + +#### 1.3、测试自定义的注解 + +我们在一个请求接口上指定这个注解,来测试一下效果 + +``` java +@RestController +@RequestMapping("/test/") +public class TestController { + + @RequestMapping("test") + @CheckAccount(name = "sa", pwd = "123456") + public SaResult test() { + System.out.println("------------进来了"); + return SaResult.ok(); + } + +} +``` + +启动项目,使用浏览器访问此接口。 + +先来个错误的账号密码访问测试一下:[http://localhost:8081/test/test?name=sa&pwd=123](http://localhost:8081/test/test?name=sa&pwd=123) + +返回结果: + +``` js +{ + "code": 500, + "msg": "账号或密码错误,未通过校验", + "data": null +} +``` + +使用正确账号密码测试访问:[http://localhost:8081/test/test?name=sa&pwd=123456](http://localhost:8081/test/test?name=sa&pwd=123456) + +返回结果: + +``` js +{ + "code": 200, + "msg": "ok", + "data": null +} +``` + + + +### 2、使用自定义注解优化多账号鉴权 + +在之前的 [ 多账号鉴权 ] 章节,我们介绍了利用 “spring 注解处理器” 达到注解合并的目的,从而简化多账号体系下的注解鉴权写法。 + +此种方案比较简单,但是也有一些缺点。 +- 1、强依赖 Spring,无法在非 Spring 环境中使用。 +- 2、注解递归检查可能会造成一些性能下降。 +- 3、扩展性较低,只能略微简化框架内置好的注解写法,无法灵活扩展功能。 + +此处我们再演示一种方案,使用自定义注解的方式达到相同的目的。 + + +#### 2.1、首先定义注解 + +``` java +/** + * 登录认证(User版):只有登录之后才能进入该方法 + *

可标注在函数、类上(效果等同于标注在此类的所有方法上) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE}) +public @interface SaUserCheckLogin { + +} +``` + +#### 2.2、定义注解处理器 +``` java +/** + * 注解 SaUserCheckLogin 的处理器 + */ +@Component +public class SaUserCheckLoginHandler implements SaAnnotationAbstractHandler { + + @Override + public Class getHandlerAnnotationClass() { + return SaUserCheckLogin.class; + } + + @Override + public void checkMethod(SaUserCheckLogin at, AnnotatedElement element) { + SaCheckLoginHandler._checkMethod(StpUserUtil.TYPE); + } + +} +``` + +#### 2.3、使用新注解 +接下来就可以使用我们的自定义注解了: + +``` java +// 使用 @SaUserCheckLogin 的效果等同于使用:@SaCheckLogin(type = "user") +@SaUserCheckLogin +@RequestMapping("info") +public String info() { + return "查询用户信息"; +} +``` + +注:其它注解 `@SaCheckRole("xxx")`、`@SaCheckPermission("xxx")` 同理, 完整示例参考 Gitee 代码: +[自定义注解](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation)。 + + + + + + + + + + + +