SaAnnotationAbstractHandler 由抽象类改为接口

This commit is contained in:
click33 2024-08-03 19:08:51 +08:00
parent 834e1d5b34
commit cd0c20793a
24 changed files with 1253 additions and 0 deletions

View File

@ -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<T extends Annotation> {
/**
* 获取所要处理的注解类型
* @return /
*/
Class<T> 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);
}

View File

@ -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<SaCheckDisable> {
@Override
public Class<SaCheckDisable> 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);
}
}
}

View File

@ -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<SaCheckHttpBasic> {
@Override
public Class<SaCheckHttpBasic> 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);
}
}

View File

@ -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<SaCheckHttpDigest> {
@Override
public Class<SaCheckHttpDigest> 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();
}
}

View File

@ -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<SaCheckLogin> {
@Override
public Class<SaCheckLogin> 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();
}
}

View File

@ -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<SaCheckOr> {
@Override
public Class<SaCheckOr> 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<Annotation> 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<SaTokenException> 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);
}
}

View File

@ -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<SaCheckPermission> {
@Override
public Class<SaCheckPermission> 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;
}
}
}

View File

@ -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<SaCheckRole> {
@Override
public Class<SaCheckRole> 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);
}
}
}

View File

@ -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<SaCheckSafe> {
@Override
public Class<SaCheckSafe> 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);
}
}

View File

@ -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<SaIgnore> {
@Override
public Class<SaIgnore> getHandlerAnnotationClass() {
return SaIgnore.class;
}
@Override
public void checkMethod(SaIgnore at, AnnotatedElement element) {
_checkMethod();
}
public static void _checkMethod() {
SaRouter.stop();
}
}

View File

@ -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<Class<?>, 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<Class<?>, 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<Class<?>, SaAnnotationAbstractHandler<?>> entry: annotationHandlerMap.entrySet()) {
// 先校验 Method 所属 Class 上的注解
Annotation classTakeAnnotation = instance.getAnnotation.apply(method.getDeclaringClass(), (Class<Annotation>)entry.getKey());
if(classTakeAnnotation != null) {
entry.getValue().check(classTakeAnnotation, method.getDeclaringClass());
}
// 再校验 Method 上的注解
Annotation methodTakeAnnotation = instance.getAnnotation.apply(method, (Class<Annotation>)entry.getKey());
if(methodTakeAnnotation != null) {
entry.getValue().check(methodTakeAnnotation, method);
}
}
};
/**
* 从元素上获取注解
*/
public SaGetAnnotationFunction getAnnotation = (element, annotationClass)->{
// 默认使用jdk的注解处理器
return element.getAnnotation(annotationClass);
};
}

View File

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

View File

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

View File

@ -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版)客户端必须完成二级认证之后才能进入该方法否则将被抛出异常
*
* <p> 可标注在方法类上效果等同于标注在此类的所有方法上
*
* @author click33
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface SaUserCheckSafe {
/**
* 要校验的服务
*
* @return /
*/
String value() default SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE;
}

View File

@ -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<CheckAccount> {
// 指定这个处理器要处理哪个注解
@Override
public Class<CheckAccount> 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("账号或密码错误,未通过校验");
}
}
}

View File

@ -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<SaUserCheckLogin> {
@Override
public Class<SaUserCheckLogin> getHandlerAnnotationClass() {
return SaUserCheckLogin.class;
}
@Override
public void checkMethod(SaUserCheckLogin at, AnnotatedElement element) {
SaCheckLoginHandler._checkMethod(StpUserUtil.TYPE);
}
}

View File

@ -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<SaUserCheckPermission> {
@Override
public Class<SaUserCheckPermission> getHandlerAnnotationClass() {
return SaUserCheckPermission.class;
}
@Override
public void checkMethod(SaUserCheckPermission at, AnnotatedElement element) {
SaCheckPermissionHandler._checkMethod(StpUserUtil.TYPE, at.value(), at.mode(), at.orRole());
}
}

View File

@ -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<SaUserCheckRole> {
@Override
public Class<SaUserCheckRole> getHandlerAnnotationClass() {
return SaUserCheckRole.class;
}
@Override
public void checkMethod(SaUserCheckRole at, AnnotatedElement element) {
SaCheckRoleHandler._checkMethod(StpUserUtil.TYPE, at.value(), at.mode());
}
}

View File

@ -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<SaUserCheckSafe> {
@Override
public Class<SaUserCheckSafe> getHandlerAnnotationClass() {
return SaUserCheckSafe.class;
}
@Override
public void checkMethod(SaUserCheckSafe at, AnnotatedElement element) {
SaCheckSafeHandler._checkMethod(StpUserUtil.TYPE, at.value());
}
}

View File

@ -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版)只有登录之后才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上
* @author click33
*
*/
@SaCheckLogin(type = StpUserUtil.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckLogin {
}

View File

@ -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版)必须具有指定权限才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上
* @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;
/**
* 在权限校验不通过时的次要选择两者只要其一校验成功即可通过校验
*
* <p>
* 例1@SaCheckPermission(value="user-add", orRole="admin")
* 代表本次请求只要具有 user-add权限 admin角色 其一即可通过校验
* </p>
*
* <p>
* 例2 orRole = {"admin", "manager", "staff"}具有三个角色其一即可 <br>
* 例3 orRole = {"admin, manager, staff"}必须三个角色同时具备
* </p>
*
* @return /
*/
@AliasFor(annotation = SaCheckPermission.class)
String[] orRole() default {};
}

View File

@ -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版)必须具有指定角色标识才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上
* @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;
}

View File

@ -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版)客户端必须完成二级认证之后才能进入该方法否则将被抛出异常
*
* <p> 可标注在方法类上效果等同于标注在此类的所有方法上
*
* @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;
}

View File

@ -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<CheckAccount> {
// 指定这个处理器要处理哪个注解
@Override
public Class<CheckAccount> 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版):只有登录之后才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckLogin {
}
```
#### 2.2、定义注解处理器
``` java
/**
* 注解 SaUserCheckLogin 的处理器
*/
@Component
public class SaUserCheckLoginHandler implements SaAnnotationAbstractHandler<SaUserCheckLogin> {
@Override
public Class<SaUserCheckLogin> 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)。