【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

相关文章

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Apache Ignite 与 Spring Boot 集成详细指南

《ApacheIgnite与SpringBoot集成详细指南》ApacheIgnite官方指南详解如何通过SpringBootStarter扩展实现自动配置,支持厚/轻客户端模式,简化Ign... 目录 一、背景:为什么需要这个集成? 二、两种集成方式(对应两种客户端模型) 三、方式一:自动配置 Thick

Python实现网格交易策略的过程

《Python实现网格交易策略的过程》本文讲解Python网格交易策略,利用ccxt获取加密货币数据及backtrader回测,通过设定网格节点,低买高卖获利,适合震荡行情,下面跟我一起看看我们的第一... 网格交易是一种经典的量化交易策略,其核心思想是在价格上下预设多个“网格”,当价格触发特定网格时执行买

Python标准库之数据压缩和存档的应用详解

《Python标准库之数据压缩和存档的应用详解》在数据处理与存储领域,压缩和存档是提升效率的关键技术,Python标准库提供了一套完整的工具链,下面小编就来和大家简单介绍一下吧... 目录一、核心模块架构与设计哲学二、关键模块深度解析1.tarfile:专业级归档工具2.zipfile:跨平台归档首选3.

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Linux如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

Spring WebClient从入门到精通

《SpringWebClient从入门到精通》本文详解SpringWebClient非阻塞响应式特性及优势,涵盖核心API、实战应用与性能优化,对比RestTemplate,为微服务通信提供高效解决... 目录一、WebClient 概述1.1 为什么选择 WebClient?1.2 WebClient 与

Java.lang.InterruptedException被中止异常的原因及解决方案

《Java.lang.InterruptedException被中止异常的原因及解决方案》Java.lang.InterruptedException是线程被中断时抛出的异常,用于协作停止执行,常见于... 目录报错问题报错原因解决方法Java.lang.InterruptedException 是 Jav

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We