mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-10-21 19:17:25 +08:00
feat(plugin): 新增 sa-token-spring-el 插件,用于支持 SpEL 表达式注解鉴权
This commit is contained in:
34
sa-token-plugin/sa-token-spring-el/pom.xml
Normal file
34
sa-token-plugin/sa-token-spring-el/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-plugin</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>sa-token-spring-el</name>
|
||||
<artifactId>sa-token-spring-el</artifactId>
|
||||
<description>sa-token authentication by spring-el</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- sa-token-spring-boot-starter -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-core</artifactId>
|
||||
</dependency>
|
||||
<!-- spring-boot-starter-aop -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
</project>
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 注解鉴权:根据 EL 表达式执行鉴权
|
||||
*
|
||||
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.40.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
public @interface SaCheckEL {
|
||||
|
||||
/**
|
||||
* 需要执行的 EL 表达式
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
}
|
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.annotation.SaCheckEL;
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.strategy.SaAnnotationStrategy;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.context.expression.MapAccessor;
|
||||
import org.springframework.context.expression.MethodBasedEvaluationContext;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Sa-Token 注解鉴权 EL 表达式 AOP 切入 (用于处理 @SaCheckEL 注解)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.40.0
|
||||
*/
|
||||
@Aspect
|
||||
public class SaCheckELAspect implements BeanFactoryAware {
|
||||
|
||||
/**
|
||||
* 表达式解析器 (用于解析 EL 表达式)
|
||||
*/
|
||||
private final ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
/**
|
||||
* 参数名发现器 (用于获取方法参数名)
|
||||
*/
|
||||
private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
|
||||
|
||||
/**
|
||||
* Spring Bean 工厂 (用于解析 Spring 容器中的 Bean 对象)
|
||||
*/
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 前置通知 (所有被 SaCheckEL 注解修饰的方法或类)
|
||||
*
|
||||
* @param joinPoint /
|
||||
*/
|
||||
@Before("@within(cn.dev33.satoken.annotation.SaCheckEL) || @annotation(cn.dev33.satoken.annotation.SaCheckEL)")
|
||||
public void atBefore(JoinPoint joinPoint) {
|
||||
|
||||
// 获取方法签名与参数列表
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
|
||||
// 如果标注了 @SaIgnore 注解,则跳过,代表不进行校验
|
||||
if(SaAnnotationStrategy.instance.isAnnotationPresent.apply(method, SaIgnore.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1、根数据对象构建
|
||||
// 构建校验上下文根数据对象
|
||||
SaCheckELRootMap rootMap = new SaCheckELRootMap(method, extractArgs(method, args), joinPoint.getTarget() );
|
||||
|
||||
// 添加 this 指针指向注解函数所在类,使之可以在表达式中通过 this.xx 访问类的属性和方法 (与Target一致,此处只是为了更加语义化)
|
||||
rootMap.put(SaCheckELRootMap.KEY_THIS, joinPoint.getTarget());
|
||||
|
||||
// 添加全局默认的 StpLogic 对象,使之可以在表达式中通过 stp.checkLogin() 方式调用校验方法
|
||||
rootMap.put(SaCheckELRootMap.KEY_STP, StpUtil.getStpLogic());
|
||||
|
||||
// 添加 JoinPoint 对象,使开发者在扩展时可以根据 JoinPoint 对象获取更多信息
|
||||
rootMap.put(SaCheckELRootMap.KEY_JOIN_POINT, joinPoint);
|
||||
|
||||
// 执行开发者自定义的增强策略
|
||||
SaAnnotationStrategy.instance.checkELRootMapExtendFunction.accept(rootMap);
|
||||
|
||||
// 2、表达式解析方案构建
|
||||
// 创建表达式解析上下文
|
||||
MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(rootMap, method, args, pnd);
|
||||
|
||||
// 添加属性访问器,使之可以解析 Map 对象的属性作为根上下文
|
||||
context.addPropertyAccessor(new MapAccessor());
|
||||
|
||||
// 设置 Bean 解析器,使之可以在表达式中引用 Spring 容器管理的所有 Bean 对象
|
||||
context.setBeanResolver(new BeanFactoryResolver(beanFactory));
|
||||
|
||||
// 3、开始校验
|
||||
// 先校验 Method 所属 Class 上的注解表达式
|
||||
SaCheckEL ofClass = (SaCheckEL) SaAnnotationStrategy.instance.getAnnotation.apply(method.getDeclaringClass(), SaCheckEL.class);
|
||||
if (ofClass != null) {
|
||||
parser.parseExpression(ofClass.value()).getValue(context);
|
||||
}
|
||||
|
||||
// 再校验 Method 上的注解表达式
|
||||
SaCheckEL ofMethod = (SaCheckEL) SaAnnotationStrategy.instance.getAnnotation.apply(method, SaCheckEL.class);
|
||||
if (ofMethod != null) {
|
||||
parser.parseExpression(ofMethod.value()).getValue(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果是可变长参数,则展开并返回,否则原样返回
|
||||
*
|
||||
* @param method /
|
||||
* @param args /
|
||||
* @return /
|
||||
*/
|
||||
private Object[] extractArgs(Method method, Object[] args) {
|
||||
if (!method.isVarArgs()) {
|
||||
return args;
|
||||
} else {
|
||||
Object[] varArgs = ObjectUtils.toObjectArray(args[args.length - 1]);
|
||||
Object[] combinedArgs = new Object[args.length - 1 + varArgs.length];
|
||||
System.arraycopy(args, 0, combinedArgs, 0, args.length - 1);
|
||||
System.arraycopy(varArgs, 0, combinedArgs, args.length - 1, varArgs.length);
|
||||
return combinedArgs;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Sa-Token 注解鉴权 EL 表达式解析器的根数据对象
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.40.0
|
||||
*/
|
||||
public class SaCheckELRootMap extends HashMap<String, Object> {
|
||||
|
||||
/**
|
||||
* KEY标记:被切入的函数
|
||||
*/
|
||||
public static final String KEY_METHOD = "method";
|
||||
|
||||
/**
|
||||
* KEY标记:被切入的函数参数
|
||||
*/
|
||||
public static final String KEY_ARGS = "args";
|
||||
|
||||
/**
|
||||
* KEY标记:被切入的目标对象
|
||||
*/
|
||||
public static final String KEY_TARGET = "target";
|
||||
|
||||
/**
|
||||
* KEY标记:注解所在类对象引用
|
||||
*/
|
||||
public static final String KEY_THIS = "this";
|
||||
|
||||
/**
|
||||
* KEY标记:全局默认 StpLogic 对象
|
||||
*/
|
||||
public static final String KEY_STP = "stp";
|
||||
|
||||
/**
|
||||
* KEY标记:本次切入的 JoinPoint 对象
|
||||
*/
|
||||
public static final String KEY_JOIN_POINT = "joinPoint";
|
||||
|
||||
public SaCheckELRootMap(Method method, Object[] args, Object target) {
|
||||
this.put(KEY_METHOD, method);
|
||||
this.put(KEY_ARGS, args);
|
||||
this.put(KEY_TARGET, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 被切入的函数
|
||||
*
|
||||
* @return method 被切入的函数
|
||||
*/
|
||||
public Method getMethod() {
|
||||
return (Method) this.get(KEY_METHOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 被切入的函数参数
|
||||
*
|
||||
* @return args 被切入的函数参数
|
||||
*/
|
||||
public Object[] getArgs() {
|
||||
return (Object[]) this.get(KEY_ARGS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 被切入的目标对象
|
||||
*
|
||||
* @return target 被切入的目标对象
|
||||
*/
|
||||
public Object getTarget() {
|
||||
return this.get(KEY_TARGET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 注解所在类对象引用
|
||||
*
|
||||
* @return this 注解所在类对象引用
|
||||
*/
|
||||
public Object getThis() {
|
||||
return this.get(KEY_THIS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本次切入的 JoinPoint 对象
|
||||
*/
|
||||
public Object getJoinPoint() {
|
||||
return this.get(KEY_JOIN_POINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言函数, 表达式执行结果为true才能通过
|
||||
*
|
||||
* @param flag 执行结果
|
||||
*/
|
||||
public void NEED(boolean flag) {
|
||||
NEED(flag, SaErrorCode.CODE_UNDEFINED, "未通过 EL 表达式校验");
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言函数, 表达式执行结果为true才能通过,并在未通过时抛出 SaTokenException 异常,异常描述信息为 errorMessage
|
||||
*
|
||||
* @param flag 执行结果
|
||||
*/
|
||||
public void NEED(boolean flag, String errorMessage) {
|
||||
NEED(flag, SaErrorCode.CODE_UNDEFINED, errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言函数, 表达式执行结果为true才能通过,并在未通过时抛出 SaTokenException 异常,异常码为 errorCode,异常描述信息为 errorMessage
|
||||
*
|
||||
* @param flag 执行结果
|
||||
*/
|
||||
public void NEED(boolean flag, int errorCode, String errorMessage) {
|
||||
if(!flag) {
|
||||
throw new SaTokenException(errorCode, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.dev33.satoken.aop.SaCheckELAspect
|
@@ -0,0 +1 @@
|
||||
cn.dev33.satoken.aop.SaCheckELAspect
|
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"cn.dev33.satoken.annotation.SaCheckEL@value": {
|
||||
"method": {
|
||||
"parameters": true,
|
||||
"parametersPrefix": [
|
||||
"p",
|
||||
"a"
|
||||
]
|
||||
},
|
||||
"fields": {
|
||||
"root": "cn.dev33.satoken.aop.SaCheckELRootMap",
|
||||
"stp": "cn.dev33.satoken.stp.StpLogic"
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user