使 sa-token-spring-aop 插件支持自定义注解鉴权

This commit is contained in:
click33
2024-08-04 01:11:40 +08:00
parent c38eb0c68c
commit aa2e9a5c50
9 changed files with 204 additions and 131 deletions

View File

@@ -0,0 +1,98 @@
package cn.dev33.satoken.aop;
import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler;
import cn.dev33.satoken.strategy.SaAnnotationStrategy;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* Sa-Token AOP 环绕切入 Bean 注册
* <p>
* 参考资料:<br>
* https://www.jb51.net/program/297714rev.htm <br>
* https://www.bilibili.com/video/BV1WZ421W7Qx <br>
* https://blog.csdn.net/Tomwildboar/article/details/139199801 <br>
* </p>
*
* @author click33
* @since 2024/8/3
*/
@Configuration
public class SaAopPointcutAdvisorBeanRegister {
/**
* Advisor 静态全局引用
*/
public static SaAroundAnnotationPointcutAdvisor saAroundAnnoAdvisor;
@Bean
public SaAroundAnnotationPointcutAdvisor saAroundAnnotationHandlePointcutAdvisor (List<SaAnnotationAbstractHandler<?>> handlerList) {
SaAroundAnnotationPointcutAdvisor advisor = new SaAroundAnnotationPointcutAdvisor();
// 定义切入规则
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
String expression = calcExpression(handlerList);
pointcut.setExpression(expression);
advisor.setPointcut(pointcut);
// 定义执行的方法
advisor.setAdvice(new SaAroundAnnotationMethodInterceptor());
// 保存全局引用
SaAopPointcutAdvisorBeanRegister.saAroundAnnoAdvisor = advisor;
return advisor;
}
/**
* 计算切入表达式
* @param appendHandlerList 追加的 SaAnnotationAbstractHandler 处理器
* @return /
*/
public static String calcExpression(List<SaAnnotationAbstractHandler<?>> appendHandlerList) {
// 框架内置的
List<Class<?>> list = new ArrayList<>(SaAnnotationStrategy.instance.annotationHandlerMap.keySet());
// 额外追加的
if(appendHandlerList != null) {
for (SaAnnotationAbstractHandler<?> handler : appendHandlerList) {
Class<?> cls = handler.getHandlerAnnotationClass();
if(!list.contains(cls)) {
list.add(handler.getHandlerAnnotationClass());
}
}
}
// 计算
return calcClassListExpression(list);
}
/**
* 计算 class 列表的切入表达式,
* 最终样例形如:
<pre>
public static final String POINTCUT_SIGN =
"@within(cn.dev33.satoken.annotation.SaCheckLogin) || @annotation(cn.dev33.satoken.annotation.SaCheckLogin) || "
+ "@within(cn.dev33.satoken.annotation.SaCheckRole) || @annotation(cn.dev33.satoken.annotation.SaCheckRole) || "
+ "@within(cn.dev33.satoken.annotation.SaCheckPermission) || @annotation(cn.dev33.satoken.annotation.SaCheckPermission)";
</pre>
* @param list /
* @return /
*/
public static String calcClassListExpression(List<Class<?>> list) {
String pointcutExpression = "";
for (Class<?> cls : list) {
if(!pointcutExpression.isEmpty()) {
pointcutExpression += " || ";
}
pointcutExpression += "@within(" + cls.getName() + ") || @annotation(" + cls.getName() + ")";
}
return pointcutExpression;
}
}

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.aop;
import cn.dev33.satoken.exception.StopMatchException;
import cn.dev33.satoken.strategy.SaAnnotationStrategy;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
/**
* Sa-Token 注解方法拦截器 AOP环绕切入
*
* @author click33
* @since 1.39.0
*/
public class SaAroundAnnotationMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 注解鉴权
try{
Method method = invocation.getMethod();
SaAnnotationStrategy.instance.checkMethodAnnotation.accept(method);
} catch (StopMatchException ignored) {
}
// 执行原有防范
return invocation.proceed();
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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.aop;
import org.springframework.aop.support.DefaultPointcutAdvisor;
/**
* Sa-Token 注解方法 Advisor AOP环绕切入
*
* @author click33
* @since 1.39.0
*/
public class SaAroundAnnotationPointcutAdvisor extends DefaultPointcutAdvisor {
}

View File

@@ -1,99 +0,0 @@
/*
* 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.aop;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaTokenConsts;
/**
* Sa-Token 基于 Spring Aop 的注解鉴权
*
* <p>
* 注意:在打开 注解鉴权 时AOP 模式与拦截器模式不可同时使用,否则会出现在 Controller 层重复鉴权两次的问题
* </p>
*
* @author click33
* @since 1.19.0
*/
@Aspect
@Component
@Order(SaTokenConsts.ASSEMBLY_ORDER)
public class SaCheckAspect {
/**
* 构建
*/
public SaCheckAspect() {
}
/**
* 定义AOP签名 (切入所有使用 Sa-Token 鉴权注解的方法)
*/
public static final String POINTCUT_SIGN =
"@within(cn.dev33.satoken.annotation.SaCheckLogin) || @annotation(cn.dev33.satoken.annotation.SaCheckLogin) || "
+ "@within(cn.dev33.satoken.annotation.SaCheckRole) || @annotation(cn.dev33.satoken.annotation.SaCheckRole) || "
+ "@within(cn.dev33.satoken.annotation.SaCheckPermission) || @annotation(cn.dev33.satoken.annotation.SaCheckPermission) || "
+ "@within(cn.dev33.satoken.annotation.SaCheckSafe) || @annotation(cn.dev33.satoken.annotation.SaCheckSafe) || "
+ "@within(cn.dev33.satoken.annotation.SaCheckDisable) || @annotation(cn.dev33.satoken.annotation.SaCheckDisable) || "
+ "@within(cn.dev33.satoken.annotation.SaCheckHttpDigest) || @annotation(cn.dev33.satoken.annotation.SaCheckHttpDigest) || "
+ "@within(cn.dev33.satoken.annotation.SaCheckOr) || @annotation(cn.dev33.satoken.annotation.SaCheckOr) || "
+ "@within(cn.dev33.satoken.annotation.SaCheckHttpBasic) || @annotation(cn.dev33.satoken.annotation.SaCheckHttpBasic)";
/**
* 声明AOP签名
*/
@Pointcut(POINTCUT_SIGN)
public void pointcut() {
}
/**
* 环绕切入
*
* @param joinPoint 切面对象
* @return 底层方法执行后的返回值
* @throws Throwable 底层方法抛出的异常
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取对应的 Method 处理函数
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 如果此 Method 或其所属 Class 标注了 @SaIgnore则忽略掉鉴权
if(SaStrategy.instance.isAnnotationPresent.apply(method, SaIgnore.class)) {
// ...
} else {
// 注解鉴权
SaStrategy.instance.checkMethodAnnotation.accept(method);
}
// 执行原有逻辑
return joinPoint.proceed();
}
}

View File

@@ -1 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.aop.SaCheckAspect
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.dev33.satoken.aop.SaAopPointcutAdvisorBeanRegister

View File

@@ -1 +1 @@
cn.dev33.satoken.aop.SaCheckAspect
cn.dev33.satoken.aop.SaAopPointcutAdvisorBeanRegister