【SpringBoot应用篇】【AOP+注解】SpringBoot+SpEL表达式基于注解实现权限控制
- [Spring SpEL](#Spring SpEL)
- SpEL表达式实现权限控制
Spring SpEL
Spring 表达式语言 SpEL 是一种非常强大的表达式语言,它支持在运行时查询和操作对象图。 它提供了许多高级功能,例如方法调用和基本的字符串模板功能。表达式语言给静态Java语言增加了动态功能。
Spring 表达式语言最初是为 Spring 社区创建的,它拥有一种受良好支持的表达式语言,可用于 Spring 产品组合中的所有产品。 虽然 SpEL 是 Spring 产品组合中表达式评估的基础,但它不直接与 Spring 绑定,可以独立使用
Spring Security框架中启用prePost
注解的支持就是使用SpEL表达式实现的权限控制
- @PreAuthorize("hasAuthority('save')")
- @PreAuthorize("isAnonymous()")
- @PreAuthorize("isAuthenticated()")
SpEL支持如下表达式:
- 基本表达式: 字面量表达式、关系,逻辑与算数运算表达式、字符串连接及截取表达式、三目运算及Elivis表达式、正则表达式、括号优先级表达式
- 类相关表达式: 类类型表达式、类实例化、instanceof表达式、变量定义及引用、赋值表达式、自定义函数、对象属性存取及安全导航表达式、对象方法调用、Bean引用
- 集合相关表达式:内联List、内联数组、集合,字典访问、列表,字典,数组修改、集合投影、集合选择;不支持多维内联数组初始化;不支持内联字典定义
- 其他表达式:模板表达式。
注:SpEL表达式中的关键字是不区分大小写的。
基本表达式
java
/**
* 基本表达式 : 字面量表达式
*/
@Test
public void test01() {
// 1)创建解析器:SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默认实现;
ExpressionParser parser = new SpelExpressionParser();
// 2)解析表达式:使用ExpressionParser的parseExpression来解析相应的表达式为Expression对象。
Expression expression = parser.parseExpression("1+2");
// 3)求值:通过Expression接口的getValue方法根据上下文获得表达式值。
System.out.println(expression.getValue());
String str1 = parser.parseExpression("'Hello World!'").getValue(String.class);
int int1 = parser.parseExpression("1").getValue(Integer.class);
long long1 = parser.parseExpression("-1L").getValue(long.class);
float float1 = parser.parseExpression("1.1").getValue(Float.class);
double double1 = parser.parseExpression("1.1E+2").getValue(double.class);
int hex1 = parser.parseExpression("0xa").getValue(Integer.class);
long hex2 = parser.parseExpression("0xaL").getValue(long.class);
boolean true1 = parser.parseExpression("true").getValue(boolean.class);
boolean false1 = parser.parseExpression("false").getValue(boolean.class);
Object null1 = parser.parseExpression("null").getValue(Object.class);
System.out.println("str1=" + str1);
System.out.println("int1=" + int1);
System.out.println("long1=" + long1);
System.out.println("float1=" + float1);
System.out.println("double1=" + double1);
System.out.println("hex1=" + hex1);
System.out.println("hex2=" + hex2);
System.out.println("true1=" + true1);
System.out.println("false1=" + false1);
System.out.println("null1=" + null1);
}
java
/**
* 基本表达式 : 字面量表达式
*/
@Test
public void test02() {
// 1)创建解析器:SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默认实现;
ExpressionParser parser = new SpelExpressionParser();
// 2)解析表达式:使用ExpressionParser的parseExpression来解析相应的表达式为Expression对象。
Expression expression = parser.parseExpression("('Hello' + ' World').concat(#end)");
// 3)构造上下文:准备比如变量定义等等表达式需要的上下文数据。
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end", "!");
// 4)求值:通过Expression接口的getValue方法根据上下文获得表达式值。
System.out.println(expression.getValue(context));
}
类相关表达式
java
@Data
@Component("ss")
public class User {
private String username;
private String address;
private Integer age;
public String sayHello(String username) {
return "hello " + username;
}
public String sayHello(Integer age) {
return "hello " + username + ";age=" + age;
}
public String sayHello() {
return "hello " + username;
}
}
java
/**
* 类相关表达式: 变量定义及引用
*/
@Test
public void test03() {
String expression = "#user.username";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("长沙");
user.setUsername("zysheep");
user.setAge(24);;
ctx.setVariable("user", user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);
}
java
@Test
public void test04() {
String expression = "username";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("长沙");
user.setUsername("zysheep");
user.setAge(24);
// user 对象设置为 rootObject,那么表达式中就不需要 #user.
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);
}
java
/**
* 类相关表达式: 对象方法调用
*/
@Test
public void test05() {
String expression = "sayHello(99)";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("长沙");
user.setUsername("zysheep");
user.setAge(24);
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);
}
java
/**
* 类相关表达式: 对象方法调用
*
* 调用无参的 sayHello
*/
@Test
public void test06() {
String expression = "sayHello()";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("长沙");
user.setUsername("zysheep");
user.setAge(24);
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);
}
java
/**
* 类相关表达式: Bean引用
*/
@Test
public void test07() {
// 通过 SpEL 表达式来调用这个名为 ss 的 bean 中的 sayHello 方法
String expression = "@ss.sayHello('spel bean引用')";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
// 给配置的上下文环境设置一个 bean 解析器,这个 bean 解析器会自动跟进名字从 Spring 容器中找打响应的 bean 并执行对应的方法
ctx.setBeanResolver(new BeanFactoryResolver(beanFactory));
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);
}
表达式模板
java
/**
* 表达式模板:
* 模板表达式就是由字面量与一个或多个表达式块组成。每个表达式块由"前缀+表达式+后缀"形式组成,如"${1+2}"即表达式块
*
*/
@Test
public void test08() {
//创建解析器
SpelExpressionParser parser = new SpelExpressionParser();
//创建解析器上下文
ParserContext context = new TemplateParserContext("%{", "}");
Expression expression = parser.parseExpression("你好:%{#name},我们正在学习:%{#lesson}", context);
//创建表达式计算上下文
EvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.setVariable("name", "zysheep");
evaluationContext.setVariable("lesson", "SpEL表达式!");
//获取值
String value = expression.getValue(evaluationContext, String.class);
System.out.println(value);
}
SpEL表达式实现权限控制
PreAuth
java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuth {
/**
*
*
* permissionAll()-----只要配置了角色就可以访问
* hasPermission("MENU.QUERY")-----有MENU.QUERY操作权限的角色可以访问
* permitAll()-----放行所有请求
* denyAll()-----只有超级管理员角色才可访问
* hasAuth()-----只有登录后才可访问
* hasTimeAuth(1,,10)-----只有在1-10点间访问
* hasRole('管理员')-----具有管理员角色的人才能访问
* hasAllRole('管理员','总工程师')-----同时具有管理员、总工程师角色的人才能访问
*
* Spring el
* 文档地址:https://docs.spring.io/spring/docs/5.1.6.RELEASE/spring-framework-reference/core.html#expressions
*/
String value();
}
AuthFun
java
/**
* <p>
* 类相关表达式: Bean引用,参数要用单引号包括
*
* @PreAuth("@af.hasPermission('ADMIN, USER')")
* </p>
*
* @author : lyw
* @since : 2023/11/23 16:01
*/
@Component("af")
public class AuthFun {
/**
* 判断角色是否具有接口权限
*
* @return {boolean}
*/
public boolean permissionAll() {
//TODO 读取数据库权限数据
return true;
}
/**
* 判断角色是否具有接口权限
*
* @param permission 权限编号,对应菜单的MENU_CODE
* @return {boolean}
*/
public boolean hasPermission(String permission) {
return hasRole(permission);
}
/**
* 放行所有请求
*
* @return {boolean}
*/
public boolean permitAll() {
return true;
}
/**
* 只有超管角色才可访问
*
* @return {boolean}
*/
public boolean denyAll() {
return hasRole("ADMIN");
}
/**
* 是否已授权
*
* @return {boolean}
*/
public boolean hasAuth() {
return true;
}
/**
* 是否有时间授权
*
* @param start 开始时间
* @param end 结束时间
* @return {boolean}
*/
public boolean hasTimeAuth(Integer start, Integer end) {
Integer hour = DateUtil.hour(new Date(), true);
return hour >= start && hour <= end;
}
/**
* 判断是否有该角色权限
*
* @param role 单角色
* @return {boolean}
*/
public boolean hasRole(String role) {
return hasAnyRole(role);
}
/**
* 判断是否具有所有角色权限
*
* @param role 角色集合
* @return {boolean}
*/
public boolean hasAllRole(String... role) {
for (String r : role) {
if (!hasRole(r)) {
return false;
}
}
return true;
}
/**
* 判断是否有该角色权限
*
* @param role 角色集合
* @return {boolean}
*/
public boolean hasAnyRole(String... role) {
return Arrays.stream(role).anyMatch(item -> StringUtils.equals("ADMIN", item));
}
}
PreAuthAspect
java
@Aspect
@Component
public class PreAuthAspect {
@Autowired
private BeanFactory applicationContext;
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
@Pointcut("@annotation(cn.zysheep.annotation.PreAuth) || @within(cn.zysheep.annotation.PreAuth)")
public void pointcut() {}
@Around("pointcut()")
public Object preAuth(ProceedingJoinPoint point) throws Throwable {
if (handleAuth(point)) {
return point.proceed();
}
throw new BizException("用户无权限,非法用户!");
}
private boolean handleAuth(ProceedingJoinPoint point) {
MethodSignature ms = point.getSignature() instanceof MethodSignature? (MethodSignature) point.getSignature():null;
Method method = ms.getMethod();
// 读取权限注解,优先方法上,没有则读取类
PreAuth preAuth = method.getAnnotation( PreAuth.class);
// 判断表达式
String condition = preAuth.value();
if (StringUtils.isNotBlank(condition)) {
// @PreAuth("@af.hasPermission('ADMIN, USER')")
Expression expression = EXPRESSION_PARSER.parseExpression(condition);
// 方法参数值
Object[] args = point.getArgs();
StandardEvaluationContext context = getEvaluationContext(method, args);
// 获取解析计算的结果
return expression.getValue(context, Boolean.class);
}
return false;
}
/**
* 获取方法上的参数
*
* @param method 方法
* @param args 变量
* @return {SimpleEvaluationContext}
*/
private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) {
// 初始化Spel表达式上下文,并设置 AuthFun
StandardEvaluationContext context = new StandardEvaluationContext();
// 设置表达式支持spring bean
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
// 可以从session中获取登录用户的权限
// for (int i = 0; i < args.length; i++) {
// // 读取方法参数
// MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
// // 设置方法 参数名和值 为spel变量
// context.setVariable(methodParam.getParameterName(), args[i]);
// }
return context;
}
}
UserController
java
@RestController
@RequestMapping("/api")
@Slf4j
public class UserController {
@GetMapping("/save")
@PreAuth("@af.hasPermission('ADMIN')")
public R save() {
log.info("====执行保存业务逻辑=====");
return R.success();
}
@GetMapping("/get")
@PreAuth("@af.hasPermission('USER')")
public R get() {
log.info("====执行保存业务逻辑=====");
return R.success();
}
}
SpelParserUtils
java
public final class SpelParserUtils {
private static final String EXPRESSION_PREFIX = "#{";
private static final String EXPRESSION_SUFFIX = "}";
/**
* 表达式解析器
*/
private static ExpressionParser expressionParser = new SpelExpressionParser();
/**
* 参数名解析器,用于获取参数名
*/
private static DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
private SpelParserUtils(){}
/**
* 解析spel表达式
*
* @param method 方法
* @param args 参数值
* @param spelExpression 表达式
* @param clz 返回结果的类型
* @param defaultResult 默认结果
* @return 执行spel表达式后的结果
*/
public static <T> T parse(Method method, Object[] args, String spelExpression, Class<T> clz, T defaultResult) {
String[] params = parameterNameDiscoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
//设置上下文变量
for (int i = 0; i < params.length; i++) {
context.setVariable(params[i], args[i]);
}
T result = getResult(context,spelExpression,clz);
if(Objects.isNull(result)){
return defaultResult;
}
return result;
}
/**
* 解析spel表达式
*
* @param method 方法
* @param args 参数值
* @param spelExpression 表达式
* @param clz 返回结果的类型
* @return 执行spel表达式后的结果
*/
public static <T> T parse(Method method, Object[] args, String spelExpression, Class<T> clz) {
String[] params = parameterNameDiscoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
//设置上下文变量
for (int i = 0; i < params.length; i++) {
context.setVariable(params[i], args[i]);
}
return getResult(context,spelExpression,clz);
}
/**
* 解析spel表达式
*
* @param param 参数名
* @param paramValue 参数值
* @param spelExpression 表达式
* @param clz 返回结果的类型
* @return 执行spel表达式后的结果
*/
public static <T> T parse(String param, Object paramValue, String spelExpression, Class<T> clz) {
EvaluationContext context = new StandardEvaluationContext();
//设置上下文变量
context.setVariable(param, paramValue);
return getResult(context,spelExpression,clz);
}
/**
* 解析spel表达式
*
* @param param 参数名
* @param paramValue 参数值
* @param spelExpression 表达式
* @param clz 返回结果的类型
* @param defaultResult 默认结果
* @return 执行spel表达式后的结果
*/
public static <T> T parse(String param, Object paramValue,String spelExpression, Class<T> clz, T defaultResult) {
EvaluationContext context = new StandardEvaluationContext();
//设置上下文变量
context.setVariable(param, paramValue);
T result = getResult(context,spelExpression,clz);
if(Objects.isNull(result)){
return defaultResult;
}
return result;
}
/**
* 获取spel表达式后的结果
*
* @param context 解析器上下文接口
* @param spelExpression 表达式
* @param clz 返回结果的类型
* @return 执行spel表达式后的结果
*/
private static <T> T getResult(EvaluationContext context,String spelExpression, Class<T> clz){
try {
//解析表达式
Expression expression = parseExpression(spelExpression);
//获取表达式的值
return expression.getValue(context, clz);
} catch (Exception e) {
log.error(e.getMessage(),e);
}
return null;
}
/**
* 解析表达式
* @param spelExpression spel表达式
* @return
*/
private static Expression parseExpression(String spelExpression){
// 如果表达式是一个#{}表达式,需要为解析传入模板解析器上下文
if(spelExpression.startsWith(EXPRESSION_PREFIX) && spelExpression.endsWith(EXPRESSION_SUFFIX)){
return expressionParser.parseExpression(spelExpression,new TemplateParserContext());
}
return expressionParser.parseExpression(spelExpression);
}
}