【SpringBoot应用篇】【AOP+注解】SpringBoot+SpEL表达式基于注解实现权限控制

本文主要是介绍【SpringBoot应用篇】【AOP+注解】SpringBoot+SpEL表达式基于注解实现权限控制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【SpringBoot应用篇】【AOP+注解】SpringBoot+SpEL表达式基于注解实现权限控制

  • Spring SpEL
    • 基本表达式
    • 类相关表达式
    • 表达式模板
  • SpEL表达式实现权限控制
    • PreAuth
    • AuthFun
    • PreAuthAspect
    • UserController
    • SpelParserUtils

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表达式中的关键字是不区分大小写的。

基本表达式

/**
* 基本表达式 : 字面量表达式
*/
@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);
}
/**
* 基本表达式 : 字面量表达式
*/
@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));
}

类相关表达式

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

/**
* 类相关表达式: 变量定义及引用
*/
@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);
}
@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);
}
/**
* 类相关表达式: 对象方法调用
*/
@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);
}
/**
* 类相关表达式: 对象方法调用
*
* 调用无参的 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);
}
/**
* 类相关表达式: 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);
}

表达式模板

/**
* 表达式模板:
* 模板表达式就是由字面量与一个或多个表达式块组成。每个表达式块由“前缀+表达式+后缀”形式组成,如“${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

@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

/*** <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

@Aspect
@Component
public class PreAuthAspect {@Autowiredprivate 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表达式上下文,并设置 AuthFunStandardEvaluationContext context = new StandardEvaluationContext();// 设置表达式支持spring beancontext.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

@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

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

这篇关于【SpringBoot应用篇】【AOP+注解】SpringBoot+SpEL表达式基于注解实现权限控制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/528336

相关文章

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

Python实现批量CSV转Excel的高性能处理方案

《Python实现批量CSV转Excel的高性能处理方案》在日常办公中,我们经常需要将CSV格式的数据转换为Excel文件,本文将介绍一个基于Python的高性能解决方案,感兴趣的小伙伴可以跟随小编一... 目录一、场景需求二、技术方案三、核心代码四、批量处理方案五、性能优化六、使用示例完整代码七、小结一、

Java实现将HTML文件与字符串转换为图片

《Java实现将HTML文件与字符串转换为图片》在Java开发中,我们经常会遇到将HTML内容转换为图片的需求,本文小编就来和大家详细讲讲如何使用FreeSpire.DocforJava库来实现这一功... 目录前言核心实现:html 转图片完整代码场景 1:转换本地 HTML 文件为图片场景 2:转换 H

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java

C#使用Spire.Doc for .NET实现HTML转Word的高效方案

《C#使用Spire.Docfor.NET实现HTML转Word的高效方案》在Web开发中,HTML内容的生成与处理是高频需求,然而,当用户需要将HTML页面或动态生成的HTML字符串转换为Wor... 目录引言一、html转Word的典型场景与挑战二、用 Spire.Doc 实现 HTML 转 Word1

C#实现一键批量合并PDF文档

《C#实现一键批量合并PDF文档》这篇文章主要为大家详细介绍了如何使用C#实现一键批量合并PDF文档功能,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言效果展示功能实现1、添加文件2、文件分组(书签)3、定义页码范围4、自定义显示5、定义页面尺寸6、PDF批量合并7、其他方法

PHP应用中处理限流和API节流的最佳实践

《PHP应用中处理限流和API节流的最佳实践》限流和API节流对于确保Web应用程序的可靠性、安全性和可扩展性至关重要,本文将详细介绍PHP应用中处理限流和API节流的最佳实践,下面就来和小编一起学习... 目录限流的重要性在 php 中实施限流的最佳实践使用集中式存储进行状态管理(如 Redis)采用滑动