news 2026/9/8 1:01:37

SpringBoot + SpEL,轻松搞定复杂权限控制,非常优雅!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot + SpEL,轻松搞定复杂权限控制,非常优雅!

对于在Springboot中,利用自定义注解+切面来实现接口权限的控制这个大家应该都很熟悉,也有大量的博客来介绍整个的实现过程,整体来说思路如下:

  1. 自定义一个权限校验的注解,包含参数value

  2. 配置在对应的接口上

  3. 定义一个切面类,指定切点

  4. 在切入的方法体里写上权限判断的逻辑

乍一看,没毛病,学到了,学到了~,收藏起来。但是呢,等到实际用到的时候就傻眼了,为什么呢?在实际的开发中,你会发现,对于权限校验的需求场景是很多的,比如:

  1. 只要配置了任何角色,就可以访问

  2. 有某个权限就可以访问

  3. 放行所有请求

  4. 只有超级管理员角色才可以访问

  5. 只有登录后才可以访问

  6. 在指定时间段内可以访问

  7. 有某个角色的情况下才可以访问

  8. 同时具有指定的多个角色情况下才可以访问

傻眼了不,按照上面的实现逻辑的话怎么搞?加注解?写各种判断?这时候,其实我们就可以通过SpEL表达式来帮我们处理这个问题。

SpEL表达式

本文前面提到SpEL,那么到底SpEL是啥呢?

SpEL的全称为Spring Expression Language,即Spring表达式语言。是Spring3.0提供的。他最强大的功能是可以通过运行期间执行的表达式将值装配到我们的属性或构造函数之中。

如果有小伙伴之前没有接触过,不太理解这句话的含义,那么不要紧,继续往下看,通过后续的实践你就能明白他的作用了。

开搞

自定义注解

当然,万变不离其宗,自定义注解我们还是需要滴。这里呢,我们仅需要定义一个value属性用于接收表达式即可。

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

定义切面

注解定义好了,我们就需要定义切面了。这里要考虑一个点。我们希望的是如果方法上有注解,则对方法进行限制,若方法上无注解,单是类上有注解,那么类上的权限注解对该类下所有的接口生效。因此,我们切点的话要用@within注解。代码如下:

@Around( "@annotation(PreAuth注解路径) || " + "@within(PreAuth注解路径)" ) public Object preAuth(ProceedingJoinPoint point) throws Throwable { if (handleAuth(point)) { return point.proceed(); } thrownew SecureException(ResultCode.REQ_REJECT); } private boolean handleAuth(ProceedingJoinPoint point) { //TODO 逻辑判断,返回true or false }

权限校验

关键点来了。这里我们要引入SpEL。

首先,引入SpEL:
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
然后,从注解上获取我们需要的表达式:
MethodSignature ms = point.getSignature() instanceof MethodSignature? (MethodSignature) point.getSignature():null; Method method = ms.getMethod(); // 读取权限注解,优先方法上,没有则读取类 PreAuth preAuth = ClassUtil.getAnnotation(method, PreAuth.class); // 判断表达式 String condition = preAuth.value(); if (StringUtil.isNotBlank(condition)) { //TODU 表达式解析 }
表达式解析
private boolean handleAuth(ProceedingJoinPoint point) { MethodSignature ms = point.getSignature() instanceof MethodSignature? (MethodSignature) point.getSignature():null; Method method = ms.getMethod(); // 读取权限注解,优先方法上,没有则读取类 PreAuth preAuth = ClassUtil.getAnnotation(method, PreAuth.class); // 判断表达式 String condition = preAuth.value(); if (StringUtil.isNotBlank(condition)) { Expression expression = EXPRESSION_PARSER.parseExpression(condition); // 方法参数值 Object[] args = point.getArgs(); StandardEvaluationContext context = getEvaluationContext(method, args); //获取解析计算的结果 return expression.getValue(context, Boolean.class); } returnfalse; } /** * 获取方法上的参数 * * @param method 方法 * @param args 变量 * @return {SimpleEvaluationContext} */ private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) { // 初始化Sp el表达式上下文,并设置 AuthFun StandardEvaluationContext context = new StandardEvaluationContext(new AuthFun()); // 设置表达式支持spring bean context.setBeanResolver(new BeanFactoryResolver(applicationContext)); for (int i = 0; i < args.length; i++) { // 读取方法参数 MethodParameter methodParam = ClassUtil.getMethodParameter(method, i); // 设置方法 参数名和值 为spel变量 context.setVariable(methodParam.getParameterName(), args[i]); } return context; }
自定义解析方法

看完上面的解析处理是不是很蒙蔽,只看到了获取表达式,获取参数,设置参数,然后expression.getValue就完事了。有的同学会问,你权限校验的逻辑呢?

别急,关键点在这:StandardEvaluationContext context = new StandardEvaluationContext(new AuthFun());在上文代码中找到了吧。这个AuthFun就是我们进行权限校验的对象。

所以呢,我们还得在定义一下这个对象。进行具体的权限校验逻辑处理,这里定的每一个方法都可以作为表达式在权限注解中使用。代码如下:

public class AuthFun { /** * 判断角色是否具有接口权限 * * @return {boolean} */ public boolean permissionAll() { //TODO } /** * 判断角色是否具有接口权限 * * @param permission 权限编号,对应菜单的MENU_CODE * @return {boolean} */ public boolean hasPermission(String permission) { //TODO } /** * 放行所有请求 * * @return {boolean} */ public boolean permitAll() { returntrue; } /** * 只有超管角色才可访问 * * @return {boolean} */ public boolean denyAll() { return hasRole(RoleConstant.ADMIN); } /** * 是否已授权 * * @return {boolean} */ public boolean hasAuth() { if(Func.isEmpty(AuthUtil.getUser())){ // TODO 返回异常提醒 }else{ returntrue; } } /** * 是否有时间授权 * * @param start 开始时间 * @param end 结束时间 * @return {boolean} */ public boolean hasTimeAuth(Integer start, Integer end) { Integer hour = DateUtil.hour(); 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)) { returnfalse; } } returntrue; } /** * 判断是否有该角色权限 * * @param role 角色集合 * @return {boolean} */ public boolean hasAnyRole(String... role) { //获取当前登录用户 BladeUser user = AuthUtil.getUser(); if (user == null) { returnfalse; } String userRole = user.getRoleName(); if (StringUtil.isBlank(userRole)) { returnfalse; } String[] roles = Func.toStrArray(userRole); for (String r : role) { if (CollectionUtil.contains(roles, r)) { returntrue; } } returnfalse; } }

实际使用

在使用的时候,我们只需要在类上或者接口上,加上@PreAuth的直接,value值写的时候要注意一下,value应该是我们在AuthFun类中定义的方法和参数,如我们定义了解析方法hasAllRole(String... role),那么在注解中,我们就可以这样写@PreAuth("hasAllRole('角色1','角色2')"),需要注意的是,参数要用单引号包括。

@PreAuth("hasPermission('LM_QUERY,LM_QUERY_ALL')") public T 接口名称....

原理

根据上面的实际使用,可以看到。SpEL表达式解析将我们注解中的"hasAllRole('角色1','角色2')"这样的字符串,给动态解析为了hasAllRole(参数1,参数1),并调用我们注册类中同名的方法。

总结

通过SpEL的使用,让我们的权限配置校验更加灵活。当出现新的场景时,我们仅需要在自定的表达式解析类中增加对应场景的解析方法即可。相对于之前的实现方式,这不得不说是更好的一个选择。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 12:47:53

Unsloth模型版权问题:训练数据合法性避坑指南

Unsloth模型版权问题&#xff1a;训练数据合法性避坑指南 1. Unsloth 简介 Unsloth 是一个专注于提升大语言模型&#xff08;LLM&#xff09;微调效率的开源框架&#xff0c;支持包括 Llama、Qwen、Gemma、DeepSeek、TTS 和 GPT-OSS 在内的多种主流模型。它的核心优势在于显著…

作者头像 李华
网站建设 2026/9/7 5:39:52

输入照片有遮挡怎么办?unet预处理建议指南

输入照片有遮挡怎么办&#xff1f;unet预处理建议指南 1. 功能概述 本工具基于阿里达摩院 ModelScope 的 DCT-Net 模型&#xff0c;支持将真人照片转换为卡通风格。通过 UNET 架构的深度学习模型&#xff0c;实现对人像的精准提取与艺术化处理&#xff0c;适用于个人创作、社…

作者头像 李华
网站建设 2026/9/7 5:39:50

C++ undefined reference 错误全解析,掌握这7种情况再也不怕编译失败

第一章&#xff1a;C undefined reference to 错误的本质与编译原理 C 中的 "undefined reference to" 错误是链接阶段最常见的错误之一&#xff0c;通常出现在编译器成功完成编译后&#xff0c;但在链接目标文件时无法找到函数或变量的定义。该错误并非语法问题&…

作者头像 李华
网站建设 2026/9/3 0:18:27

C语言指针进阶指南(掌握数组指针与指针数组的5个关键点)

第一章&#xff1a;C语言指针进阶的核心概念 在C语言中&#xff0c;指针不仅是变量的内存地址引用&#xff0c;更是实现高效数据操作与动态内存管理的关键工具。掌握指针的进阶用法&#xff0c;有助于深入理解数组、字符串、函数指针以及复杂数据结构如链表和树的底层机制。 指…

作者头像 李华
网站建设 2026/9/7 5:39:56

为什么你的strcat导致程序崩溃?安全字符串拼接全解析

第一章&#xff1a;为什么你的strcat导致程序崩溃&#xff1f; 在C语言编程中&#xff0c; strcat 是一个常用的字符串拼接函数&#xff0c;但使用不当极易引发程序崩溃。最常见的问题源于目标缓冲区空间不足或未正确初始化&#xff0c;导致缓冲区溢出或访问非法内存。 缓冲区…

作者头像 李华
网站建设 2026/9/7 5:39:51

太阳启升之地:成山头 —— 陆海交接的东方岬角

在山东半岛最东端&#xff0c;黄海之滨&#xff0c;威海市荣成市境内&#xff0c;有一处陆海交接的岬角&#xff0c;名为成山头。这里是成山山脉延伸入海的终点&#xff0c;因其位于中国大陆海岸线的最东端&#xff0c;成为大陆上最早迎接海上日出的地点之一&#xff0c;故历史…

作者头像 李华