使 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

@ -51,7 +51,7 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -51,25 +51,25 @@ public class SecureController {
}
// RSA加密 ---- http://localhost:8081/secure/rsa
@RequestMapping("rsa")
public SaResult rsa() {
// 定义私钥和公钥
String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO+wmt01pwm9lHMdq7A8gkEigk0XKMfjv+4IjAFhWCSiTeP7dtlnceFJbkWxvbc7Qo3fCOpwmfcskwUc3VSgyiJkNJDs9ivPbvlt8IU2bZ+PBDxYxSCJFrgouVOpAr8ar/b6gNuYTi1vt3FkGtSjACFb002/68RKUTye8/tdcVilAgMBAAECgYA1COmrSqTUJeuD8Su9ChZ0HROhxR8T45PjMmbwIz7ilDsR1+E7R4VOKPZKW4Kz2VvnklMhtJqMs4MwXWunvxAaUFzQTTg2Fu/WU8Y9ha14OaWZABfChMZlpkmpJW9arKmI22ZuxCEsFGxghTiJQ3tK8npj5IZq5vk+6mFHQ6aJAQJBAPghz91Dpuj+0bOUfOUmzi22obWCBncAD/0CqCLnJlpfOoa9bOcXSusGuSPuKy5KiGyblHMgKI6bq7gcM2DWrGUCQQD3SkOcmia2s/6i7DUEzMKaB0bkkX4Ela/xrfV+A3GzTPv9bIBamu0VIHznuiZbeNeyw7sVo4/GTItq/zn2QJdBAkEA8xHsVoyXTVeShaDIWJKTFyT5dJ1TR++/udKIcuiNIap34tZdgGPI+EM1yoTduBM7YWlnGwA9urW0mj7F9e9WIQJAFjxqSfmeg40512KP/ed/lCQVXtYqU7U2BfBTg8pBfhLtEcOg4wTNTroGITwe2NjL5HovJ2n2sqkNXEio6Ji0QQJAFLW1Kt80qypMqot+mHhS+0KfdOpaKeMWMSR4Ij5VfE63WzETEeWAMQESxzhavN1WOTb3/p6icgcVbgPQBaWhGg==";
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB";
// 文本
String text = "Sa-Token 一个轻量级java权限认证框架";
// 使用公钥加密
String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text);
System.out.println("公钥加密后:" + ciphertext);
// 使用私钥解密
String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext);
System.out.println("私钥解密后:" + text2);
return SaResult.ok();
}
// @RequestMapping("rsa")
// public SaResult rsa() {
// // 定义私钥和公钥
// String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO+wmt01pwm9lHMdq7A8gkEigk0XKMfjv+4IjAFhWCSiTeP7dtlnceFJbkWxvbc7Qo3fCOpwmfcskwUc3VSgyiJkNJDs9ivPbvlt8IU2bZ+PBDxYxSCJFrgouVOpAr8ar/b6gNuYTi1vt3FkGtSjACFb002/68RKUTye8/tdcVilAgMBAAECgYA1COmrSqTUJeuD8Su9ChZ0HROhxR8T45PjMmbwIz7ilDsR1+E7R4VOKPZKW4Kz2VvnklMhtJqMs4MwXWunvxAaUFzQTTg2Fu/WU8Y9ha14OaWZABfChMZlpkmpJW9arKmI22ZuxCEsFGxghTiJQ3tK8npj5IZq5vk+6mFHQ6aJAQJBAPghz91Dpuj+0bOUfOUmzi22obWCBncAD/0CqCLnJlpfOoa9bOcXSusGuSPuKy5KiGyblHMgKI6bq7gcM2DWrGUCQQD3SkOcmia2s/6i7DUEzMKaB0bkkX4Ela/xrfV+A3GzTPv9bIBamu0VIHznuiZbeNeyw7sVo4/GTItq/zn2QJdBAkEA8xHsVoyXTVeShaDIWJKTFyT5dJ1TR++/udKIcuiNIap34tZdgGPI+EM1yoTduBM7YWlnGwA9urW0mj7F9e9WIQJAFjxqSfmeg40512KP/ed/lCQVXtYqU7U2BfBTg8pBfhLtEcOg4wTNTroGITwe2NjL5HovJ2n2sqkNXEio6Ji0QQJAFLW1Kt80qypMqot+mHhS+0KfdOpaKeMWMSR4Ij5VfE63WzETEeWAMQESxzhavN1WOTb3/p6icgcVbgPQBaWhGg==";
// String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB";
//
// // 文本
// String text = "Sa-Token 一个轻量级java权限认证框架";
//
// // 使用公钥加密
// String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text);
// System.out.println("公钥加密后:" + ciphertext);
//
// // 使用私钥解密
// String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext);
// System.out.println("私钥解密后:" + text2);
//
// return SaResult.ok();
// }
// Base64 编码 ---- http://localhost:8081/secure/base64
@RequestMapping("base64")

View File

@ -32,14 +32,14 @@ public class SaTokenConfigure implements WebMvcConfigurer {
// 注册 Sa-Token 拦截器打开注解鉴权功能
registry.addInterceptor(new SaInterceptor(handle -> {
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
// 指定一条 match 规则
SaRouter
.match("/user/**") // 拦截的 path 列表可以写多个
.notMatch("/user/doLogin", "/user/doLogin2") // 排除掉的 path 列表可以写多个
.check(r -> StpUtil.checkLogin()); // 要执行的校验动作可以写完整的 lambda 表达式
.match("/user/**") // 拦截的 path 列表可以写多个
.notMatch("/user/doLogin", "/user/doLogin2") // 排除掉的 path 列表可以写多个
.check(r -> StpUtil.checkLogin()); // 要执行的校验动作可以写完整的 lambda 表达式
// 权限校验 -- 不同模块认证不同权限
// 权限校验 -- 不同模块认证不同权限
SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
@ -49,16 +49,16 @@ public class SaTokenConfigure implements WebMvcConfigurer {
// 甚至你可以随意的写一个打印语句
SaRouter.match("/router/print", r -> System.out.println("----啦啦啦----"));
// 写一个完整的 lambda
// 写一个完整的 lambda
SaRouter.match("/router/print2", r -> {
System.out.println("----啦啦啦2----");
// ... 其它代码
// ... 其它代码
});
/*
* 相关路由都定义在 com.pj.cases.use.RouterCheckController
* 相关路由都定义在 com.pj.cases.use.RouterCheckController
*/
})).addPathPatterns("/**");
}

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