使用SpringAop切面编程通过Spel表达式实现Controller权限控制
目录
- 参考
- 一、概念
- SpEL表达式
- 二、开发
- 引入包
- 定义注解
- 定义切面
- 定义用户上下文
- 三、测试
- 新建Service在方法上注解
- 新建Service在类上注解
- 运行
参考
SpringBoot:SpEL让复杂权限控制变得很简单
一、概念
对于在Springboot中,利用自定义注解+切面来实现接口权限的控制这个大家应该都很熟悉,也有大量的博客来介绍整个的实现过程,整体来说思路如下:
- 自定义一个权限校验的注解,包含参数value
- 配置在对应的接口上
- 定义一个切面类,指定切点
- 在切入的方法体里写上权限判断的逻辑
SpEL表达式
本文前面提到SpEL,那么到底SpEL是啥呢? SpEL的全称为Spring Expression Language,即Spring表达式语言。是Spring3.0提供的。他最强大的功能是可以通过运行期间执行的表达式将值装配到我们的属性或构造函数之中。如果有小伙伴之前没有接触过,不太理解这句话的含义,那么不要紧,继续往下看,通过后续的实践你就能明白他的作用了。
二、开发
引入包
<!--spring aop + aspectj--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.0.8.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.9</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version></dependency><!--spring aop + aspectj-->
定义注解
我们仅需要定义一个value属性用于接收表达式即可。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuth {/***** permissionAll()-----只要配置了角色就可以访问* hasPermission("MENU.QUERY")-----有MENU.QUERY操作权限的角色可以访问* hasAnyPermission("MENU.QUERY","MENU.ADD")-----有MENU.QUERY操作权限的角色可以访问* permitAll()-----放行所有请求* denyAll()-----只有超级管理员角色才可访问* hasAuth()-----只有登录后才可访问* hasTimeAuth(1,,10)-----只有在1-10点间访问* hasRole(‘管理员’)-----具有管理员角色的人才能访问* hasAnyRole(‘管理员’,'总工程师')-----同时具有管理员* hasAllRole(‘管理员’,'总工程师')-----同时具有管理员、总工程师角色的人才能访问、总工程师角色的人才能访问** Spring el* 文档地址:<a href="https://docs.spring.io/spring/docs/5.1.6.RELEASE/spring-framework-reference/core.html#expressions">...</a>*/String value();}
定义切面
我们就需要定义切面了。这里要考虑一个点。我们希望的是如果方法上有注解,则对方法进行限制,若方法上无注解,单是类上有注解,那么类上的权限注解对该类下所有的接口生效。因此,我们切点的话要用@within注解
// 方式一
@Pointcut(value = "execution(* com.edevp.spring.spel.auth..*.*(..))")
// 方式二 直接切入注解
@Pointcut("@annotation(com.edevp.spring.spel.auth.annotation.PreAuth) || @within(com.edevp.spring.spel.auth.annotation.PreAuth)")
/*** 必须的注解* @create 2023/5/24*/
@Component
@Aspect
@Slf4j
public class AuthAspect {@Resourceprivate AuthContext authContext;@PostConstructpublic void init(){log.info("鉴权切面初始化");}/*** Spel解析器 关键点来了。这里我们要引入SpEL。*/private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();// @Pointcut(value = "execution(* com.edevp.spring.spel.auth..*.*(..))")@Pointcut("@annotation(com.edevp.spring.spel.auth.annotation.PreAuth) || @within(com.edevp.spring.spel.auth.annotation.PreAuth)")private void beforePointcut(){//切面,方法里的内容不会执行}/*** 前置通知* @param joinPoint 切点*/@Before(value = "beforePointcut()")public void before(JoinPoint joinPoint){//@Before是在方法执行前无法终止原方法执行log.info("前置通知。。。"+joinPoint);if (handleAuth(joinPoint)) {return;}throw new NoAuthException("没权限");}/*** 环绕通知* @param joinPoint 切点* @return Object*/@Around("beforePointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {//@Before是在方法执行前无法终止原方法执行log.info("环绕通知。。。"+joinPoint);return joinPoint.proceed();}/*** 判断是否有权限* @param point 切点* @return boolean*/@SuppressWarnings("unchecked")private boolean handleAuth(JoinPoint point) {MethodSignature ms = point.getSignature() instanceof MethodSignature? (MethodSignature) point.getSignature():null;assert ms != null;Method method = ms.getMethod();// 读取权限注解,优先方法上,没有则读取类PreAuth preAuth = method.getAnnotation(PreAuth.class);if(preAuth == null){preAuth = (PreAuth) ms.getDeclaringType().getDeclaredAnnotation(PreAuth.class);}// 判断表达式String condition = preAuth.value();if (StringUtil.isNotBlank(condition)) {Expression expression = EXPRESSION_PARSER.parseExpression(condition);StandardEvaluationContext context = new StandardEvaluationContext(authContext);// 获取解析计算的结果return Boolean.TRUE.equals(expression.getValue(context, Boolean.class));}return false;}
}
定义用户上下文
有的同学会问,你权限校验的逻辑呢?别急,关键点在这:StandardEvaluationContext context = new StandardEvaluationContext(authContext );在上文代码中找到了吧。
这个AuthFun就是我们进行权限校验的对象。所以呢,我们还得在定义一下这个对象。进行具体的权限校验逻辑处理,这里定的每一个方法都可以作为表达式在权限注解中使用。代码如下:
方法对应PreAuth中的方法字符串
@Component
public class AuthContext {private static final ThreadLocal<UserContext> USER_CONTEXT_THREAD_LOCAL = new NamedThreadLocal<>("user context");public static void setUserContext(UserContext user){USER_CONTEXT_THREAD_LOCAL.set(user);}public static UserContext getUserContext(){return USER_CONTEXT_THREAD_LOCAL.get();}public static void removeUserContext(){USER_CONTEXT_THREAD_LOCAL.remove();}/*** 判断角色是否具有接口权限** @param permission 权限编号,对应菜单的MENU_CODE* @return {boolean}*/public boolean hasPermission(String permission) {//TODOreturn hasAnyPermission(permission);}/*** 判断角色是否具有接口权限** @param permission 权限编号,对应菜单的MENU_CODE* @return {boolean}*/public boolean hasAllPermission(String... permission) {//TODOfor (String r : permission) {if (!hasPermission(r)) {return false;}}return true;}/*** 放行所有请求** @return {boolean}*/public boolean permitAll() {return true;}/*** 只有超管角色才可访问** @return {boolean}*/public boolean denyAll() {return hasRole("admin");}/*** 是否有时间授权** @param start 开始时间* @param end 结束时间* @return {boolean}*/public boolean hasTimeAuth(Integer start, Integer end) {/*Integer hour = DateUtil.hour();return hour >= start && hour <= end;*/return true;}/*** 判断是否有该角色权限** @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 roles 角色集合* @return {boolean}*/public boolean hasAnyRole(String... roles) {UserContext user = getUser();if(user!= null){return hasAnyStr(user.getRoles(),roles);}return false;}/*** 判断是否有该角色权限** @param authorities 角色集合* @return {boolean}*/public boolean hasAnyPermission(String... authorities) {UserContext user = getUser();if(user!= null){return hasAnyStr(user.getAuthorities(),authorities);}return false;}public boolean hasAnyStr(String hasStrings,String... strings) {if(StringUtil.isNotEmpty(hasStrings)){String[] roleArr = hasStrings.split(SymbolConstant.COMMA);return Arrays.stream(strings).anyMatch(r-> Arrays.asList(roleArr).contains(r));}return false;}public UserContext getUser(){UserContext o = AuthContext.getUserContext();if(o != null){return o;}return null;}}
三、测试
在使用的时候,我们只需要在类上或者接口上,加上@PreAuth的直接,value值写的时候要注意一下,value应该是我们在AuthContext 类中定义的方法和参数,如我们定义了解析方法hasAllRole(String… role),那么在注解中,我们就可以这样写@PreAuth(“hasAllRole(‘角色1’,‘角色2’)”),需要注意的是,参数要用单引号包括。
根据上面的实际使用,可以看到。SpEL表达式解析将我们注解中的"hasAllRole(‘角色1’,‘角色2’)"这样的字符串,给动态解析为了hasAllRole(参数1,参数1),并调用我们注册类中同名的方法。
新建Service在方法上注解
@Slf4j
@Component
public class AuthTestMethodService {@PreAuth("hasRole('admin')")public void testHasRole(){log.info("测试 hasRole('admin')");}@PreAuth("hasAnyRole('admin','test')")public void testHasAnyRole(){log.info("测试 testHasAnyRole('admin')");}@PreAuth("hasAllRole('admin','test')")public void testHasAllRole(){log.info("测试 testHasAllRole('admin')");}@PreAuth("hasPermission('sys:user:add')")public void testHasPermission(){log.info("测试 hasPermission('admin')");}
}
新建Service在类上注解
@Slf4j
@Component
@PreAuth("hasRole('admin')")
public class AuthTestClassService {public void testHasRole(){log.info("测试 hasRole('admin')");}}
运行
@FunctionalInterface
public interface Executer {/*** 执行*/void run();
}
...
...@SpringBootTest
public class AuthTest {@Resourceprivate AuthTestMethodService authTestService;@Resourceprivate AuthTestClassService authTestClassService;@Testvoid testInit(){AuthTestMethodService authTestService2 = new AuthTestMethodService();authTestService2.testHasRole();System.out.println("================");UserContext user = new UserContext();user.setRoles("admin,test");/* testAuth(user,()->{authTestService.testHasRole();});*/testAuth(user,()->{authTestService.testHasRole();authTestService.testHasAllRole();});user.setRoles("test");testAuth(user,()->{authTestService.testHasAnyRole();authTestService.testHasAllRole();});}@Testvoid testClass(){System.out.println("================");UserContext user = new UserContext();user.setRoles("admin,test");testAuth(user,()->{authTestClassService.testHasRole();});user.setRoles("test");testAuth(user,()->{authTestClassService.testHasRole();});}private void testAuth(UserContext user, Executer executer) {AuthContext.setUserContext(user);// 执行try{executer.run();}catch (Exception e){throw e;}finally {AuthContext.removeUserContext();}}}

相关文章:
使用SpringAop切面编程通过Spel表达式实现Controller权限控制
目录 参考一、概念SpEL表达式 二、开发引入包定义注解定义切面定义用户上下文 三、测试新建Service在方法上注解新建Service在类上注解运行 参考 SpringBoot:SpEL让复杂权限控制变得很简单 一、概念 对于在Springboot中,利用自定义注解切面来实现接口…...
Flutter:简单搞一个内容高亮
内容高亮并不陌生,特别是在搜索内容页面,可以说四处可见,就拿掘金这个应用而言,针对某一个关键字,我们搜索之后,与关键字相同的内容,则会高亮展示,如下图所示: 如上的效果…...
2023/08/10
文章目录 一、计算属性传参二、小程序、h5跳转其他平台授权三、封装popup弹窗四、实现保存海报五、下载图片和复制分享链接 一、计算属性传参 计算属性的值往往通过一个回调函数返回,但是这个回调函数是无法传递参数的,要想实现计算属性传参可以通过闭包…...
LeetCode 1289. 下降路径最小和 II:通俗易懂地讲解O(n^2) + O(1)的做法
【LetMeFly】1289.下降路径最小和 II:通俗易懂地讲解O(n^2) O(1)的做法 力扣题目链接:https://leetcode.cn/problems/minimum-falling-path-sum-ii/ 给你一个 n x n 整数矩阵 arr ,请你返回 非零偏移下降路径 数字和的最小值。 非零偏移下…...
Coin Change
一、题目 Suppose there are 5 types of coins: 50-cent, 25-cent, 10-cent, 5-cent, and 1-cent. We want to make changes with these coins for a given amount of money. For example, if we have 11 cents, then we can make changes with one 10-cent coin and one 1-c…...
2023 8 -14链表OJ
💕人面只今何处去,桃花依旧笑春风💕 作者:Mylvzi 文章主要内容:详解链表OJ题 题目一:环形链表(判断链表是否带环) 题目描述: 画图分析: 代码实现&#x…...
大数据必回之LSM树
LSM树(Log-Structured-Merge-Tree)并不像B、红黑树一样是一颗严格的树状数据结构,它其实是一种存储结构,像HBase、RocksDB这些NoSQL存储都是采用LSM树。它是一种分层、有序、面向磁盘的数据结构,核心思想是顺序写性能远…...
Vue中的Object.defineProperty详解
Vue中的Object.defineProperty是一个比较重要的方法,它是可以定义对象中属性的一个方法,相比于在对象中直接定义的对象,它更具有灵活性。 直接定义对象中的属性是这样的: let person {name:张三,address:广东,age:12,} 而Object.…...
MySQL高阶知识点(一)一条SQL【更新】语句是如何执行的
一条SQL【更新】语句是如何执行的 首先,可以确定的说,【查询】语句的那一套流程,【更新】语句也是同样会走一遍,与查询流程不一样的是, 更新语句涉及到【事务】,就必须保证事务的四大特性:ACID&…...
threejs实现模型gltf的动画效果
确保加载模型后模型有animations属性。加载完模型后,在模型中定义mixer的变量值。 // 4、加入加载器 const loader new GLTFLoader(); loader.load("./model/gltf/RobotExpressive/RobotExpressive.glb", function (gltf) {// 赋值动画给mixermixer ne…...
Harmony创建项目ohpm报错
Harmony创建FA模型的项目时报如下错: The registry is empty - edit .ohpmrc file or use "ohpm config set registry your_registry" command to set registry.解决方法: File -> Settings -> Build,Execution,Deployment -> Ohpm …...
44 | 酒店预订及取消的数据分析
1.背景介绍 数据集来自Kaggle网站上公开的Hotel booking demand项目 该数据集包含了一家城市酒店和一家度假酒店的预订信息,包括预订时间、入住时间、成人、儿童或婴儿数量、可用停车位数量等信息。 数据集容量约为12万32 本次数据分析主要包含如下内容: 总览数据,完成对…...
物联网和不断发展的ITSM
物联网将改变社会,整个技术行业关于对机器连接都通过嵌入式传感器、软件和收集和交换数据的电子设备每天都在更新中。Gartner 预测,全球将有4亿台互联设备投入使用。 无论企业采用物联网的速度如何,连接设备都将成为新常态,IT服务…...
加了ComponentScan,但是feign接口无法注入的原因
正文 正确的注入 如果发现无法注入:看看启动类Application是否有加入注解:EnableFeignClients(AppConstant.BASE_PACKAGES) 注意:EnableFeignClients和ComponentScan是两个独立的扫描,所以,如果只配置了ComponentSca…...
C#Winform中DataGridView控件显示行号实例
本文演示C#Winform中如何给DataGridView控件显示行号。 首先创建winform项目,添加DataGridView控件,给控件添加两列。 修改CS代码: using System.Windows.Forms;namespace DataGridviewDemo {public partial class Form1 : Form{public Form1(){InitializeComponent();//添…...
Stable Diffusion WebUI安装和使用教程(Windows)
目录 下载Stable Diffusion WebUI运行安装程序,双击webui.bat界面启动插件安装(github)模型下载(有些需要魔法)安装过程遇到的大坑总结参考的博客 整个过程坑巨多,我花了一个晚上的时间才全部搞定,本教程针对有编程基础…...
LeetCode 35题:搜索插入位置
题目 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 示例 1: 输入: nums [1,3,5,6], target 5 输出: 2示例 2:…...
Linux系统中常见的几种软件包管理器
软件包管理器 DPKGAPT(APT-GET)RPMYUMDNF Linux软件包管理工具是一组命令的集合,其作用是在操作系统中提供安装、更新、删除及卸载软件的方法,同时提供对系统中所有软件状态信息的查询。不同的Linux发行版会有不同的包管理器&…...
python异步IO完全指南
原地址:https://flyingbyte.cc/post/async_io/ python异步IO完全指南 做为一种并行编程的範式,异步IO在Python中非常受重视,从Python3.4到3.7快速演进。 我们已经有多线程,多进程,并发(concurrency&#x…...
打造企业或者个人IP引流法
打造企业或者个人IP引流法. 大家好,我是百收网SEO编辑:狂潮老师,今天给大家分享企业IP打造的方法 首先我们想让人知道你的企业叫什么,怎么找到你的企业 这个时候我们就需要去各大平台发布信息,客户想了解直接去搜索…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
淘宝扭蛋机小程序系统开发:打造互动性强的购物平台
淘宝扭蛋机小程序系统的开发,旨在打造一个互动性强的购物平台,让用户在购物的同时,能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机,实现旋转、抽拉等动作,增…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
