当前位置: 首页 > news >正文

使用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&#xff1a;SpEL让复杂权限控制变得很简单 一、概念 对于在Springboot中&#xff0c;利用自定义注解切面来实现接口…...

Flutter:简单搞一个内容高亮

内容高亮并不陌生&#xff0c;特别是在搜索内容页面&#xff0c;可以说四处可见&#xff0c;就拿掘金这个应用而言&#xff0c;针对某一个关键字&#xff0c;我们搜索之后&#xff0c;与关键字相同的内容&#xff0c;则会高亮展示&#xff0c;如下图所示&#xff1a; 如上的效果…...

2023/08/10

文章目录 一、计算属性传参二、小程序、h5跳转其他平台授权三、封装popup弹窗四、实现保存海报五、下载图片和复制分享链接 一、计算属性传参 计算属性的值往往通过一个回调函数返回&#xff0c;但是这个回调函数是无法传递参数的&#xff0c;要想实现计算属性传参可以通过闭包…...

LeetCode 1289. 下降路径最小和 II:通俗易懂地讲解O(n^2) + O(1)的做法

【LetMeFly】1289.下降路径最小和 II&#xff1a;通俗易懂地讲解O(n^2) O(1)的做法 力扣题目链接&#xff1a;https://leetcode.cn/problems/minimum-falling-path-sum-ii/ 给你一个 n x n 整数矩阵 arr &#xff0c;请你返回 非零偏移下降路径 数字和的最小值。 非零偏移下…...

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

&#x1f495;人面只今何处去&#xff0c;桃花依旧笑春风&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;详解链表OJ题 题目一&#xff1a;环形链表&#xff08;判断链表是否带环&#xff09; 题目描述&#xff1a; 画图分析&#xff1a; 代码实现&#x…...

大数据必回之LSM树

LSM树&#xff08;Log-Structured-Merge-Tree&#xff09;并不像B、红黑树一样是一颗严格的树状数据结构&#xff0c;它其实是一种存储结构&#xff0c;像HBase、RocksDB这些NoSQL存储都是采用LSM树。它是一种分层、有序、面向磁盘的数据结构&#xff0c;核心思想是顺序写性能远…...

Vue中的Object.defineProperty详解

Vue中的Object.defineProperty是一个比较重要的方法&#xff0c;它是可以定义对象中属性的一个方法&#xff0c;相比于在对象中直接定义的对象&#xff0c;它更具有灵活性。 直接定义对象中的属性是这样的&#xff1a; let person {name:张三,address:广东,age:12,} 而Object.…...

MySQL高阶知识点(一)一条SQL【更新】语句是如何执行的

一条SQL【更新】语句是如何执行的 首先&#xff0c;可以确定的说&#xff0c;【查询】语句的那一套流程&#xff0c;【更新】语句也是同样会走一遍&#xff0c;与查询流程不一样的是&#xff0c; 更新语句涉及到【事务】&#xff0c;就必须保证事务的四大特性&#xff1a;ACID&…...

threejs实现模型gltf的动画效果

确保加载模型后模型有animations属性。加载完模型后&#xff0c;在模型中定义mixer的变量值。 // 4、加入加载器 const loader new GLTFLoader(); loader.load("./model/gltf/RobotExpressive/RobotExpressive.glb", function (gltf) {// 赋值动画给mixermixer ne…...

Harmony创建项目ohpm报错

Harmony创建FA模型的项目时报如下错&#xff1a; The registry is empty - edit .ohpmrc file or use "ohpm config set registry your_registry" command to set registry.解决方法&#xff1a; File -> Settings -> Build,Execution,Deployment -> Ohpm …...

44 | 酒店预订及取消的数据分析

1.背景介绍 数据集来自Kaggle网站上公开的Hotel booking demand项目 该数据集包含了一家城市酒店和一家度假酒店的预订信息,包括预订时间、入住时间、成人、儿童或婴儿数量、可用停车位数量等信息。 数据集容量约为12万32 本次数据分析主要包含如下内容: 总览数据,完成对…...

物联网和不断发展的ITSM

物联网将改变社会&#xff0c;整个技术行业关于对机器连接都通过嵌入式传感器、软件和收集和交换数据的电子设备每天都在更新中。Gartner 预测&#xff0c;全球将有4亿台互联设备投入使用。 无论企业采用物联网的速度如何&#xff0c;连接设备都将成为新常态&#xff0c;IT服务…...

加了ComponentScan,但是feign接口无法注入的原因

正文 正确的注入 如果发现无法注入&#xff1a;看看启动类Application是否有加入注解&#xff1a;EnableFeignClients(AppConstant.BASE_PACKAGES) 注意&#xff1a;EnableFeignClients和ComponentScan是两个独立的扫描&#xff0c;所以&#xff0c;如果只配置了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运行安装程序&#xff0c;双击webui.bat界面启动插件安装&#xff08;github&#xff09;模型下载(有些需要魔法&#xff09;安装过程遇到的大坑总结参考的博客 整个过程坑巨多&#xff0c;我花了一个晚上的时间才全部搞定,本教程针对有编程基础…...

LeetCode 35题:搜索插入位置

题目 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 示例 1: 输入: nums [1,3,5,6], target 5 输出: 2示例 2:…...

Linux系统中常见的几种软件包管理器

软件包管理器 DPKGAPT&#xff08;APT-GET&#xff09;RPMYUMDNF Linux软件包管理工具是一组命令的集合&#xff0c;其作用是在操作系统中提供安装、更新、删除及卸载软件的方法&#xff0c;同时提供对系统中所有软件状态信息的查询。不同的Linux发行版会有不同的包管理器&…...

python异步IO完全指南

原地址&#xff1a;https://flyingbyte.cc/post/async_io/ python异步IO完全指南 做为一种并行编程的範式&#xff0c;异步IO在Python中非常受重视&#xff0c;从Python3.4到3.7快速演进。 我们已经有多线程&#xff0c;多进程&#xff0c;并发&#xff08;concurrency&#x…...

打造企业或者个人IP引流法

打造企业或者个人IP引流法. 大家好&#xff0c;我是百收网SEO编辑&#xff1a;狂潮老师&#xff0c;今天给大家分享企业IP打造的方法 首先我们想让人知道你的企业叫什么&#xff0c;怎么找到你的企业 这个时候我们就需要去各大平台发布信息&#xff0c;客户想了解直接去搜索…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

SpringCloudGateway 自定义局部过滤器

场景&#xff1a; 将所有请求转化为同一路径请求&#xff08;方便穿网配置&#xff09;在请求头内标识原来路径&#xff0c;然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

在 Spring Boot 中使用 JSP

jsp&#xff1f; 好多年没用了。重新整一下 还费了点时间&#xff0c;记录一下。 项目结构&#xff1a; pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

协议转换利器,profinet转ethercat网关的两大派系,各有千秋

随着工业以太网的发展&#xff0c;其高效、便捷、协议开放、易于冗余等诸多优点&#xff0c;被越来越多的工业现场所采用。西门子SIMATIC S7-1200/1500系列PLC集成有Profinet接口&#xff0c;具有实时性、开放性&#xff0c;使用TCP/IP和IT标准&#xff0c;符合基于工业以太网的…...

保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!

目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...

[USACO23FEB] Bakery S

题目描述 Bessie 开了一家面包店! 在她的面包店里&#xff0c;Bessie 有一个烤箱&#xff0c;可以在 t C t_C tC​ 的时间内生产一块饼干或在 t M t_M tM​ 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC​,tM​≤109)。由于空间…...

Element-Plus:popconfirm与tooltip一起使用不生效?

你们好&#xff0c;我是金金金。 场景 我正在使用Element-plus组件库当中的el-popconfirm和el-tooltip&#xff0c;产品要求是两个需要结合一起使用&#xff0c;也就是鼠标悬浮上去有提示文字&#xff0c;并且点击之后需要出现气泡确认框 代码 <el-popconfirm title"是…...