专栏名称: Java基基
一个苦练基本功的 Java 公众号,所以取名 Java 基基
目录
相关文章推荐
鲁中晨报  ·  淄博这些人,不必再谈“白”色变! ·  11 小时前  
鲁中晨报  ·  雷军,一度登顶中国首富! ·  昨天  
鲁中晨报  ·  突发坠机!已致46人遇难 ·  2 天前  
阿尔法工场研究院  ·  LV中国贪腐案细节:“服务费”转入虚构公司 ·  2 天前  
51好读  ›  专栏  ›  Java基基

SpringBoot:SpEL让复杂权限控制变得很简单!

Java基基  · 公众号  ·  · 2025-01-18 11:55

正文

👉 这是一个或许对你有用 的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入 芋道快速开发平台 知识星球。 下面是星球提供的部分资料:

👉 这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 地址:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn

来源:juejin.cn/post/
7226674759626571833


本次给大家带来的是另一个很常规但平常却很难想到的一个设计。即在权限控制中引入SpEL来让复杂的权限控制变的更简单,更灵活。

1 前言

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

  1. 自定义一个权限校验的注解,包含参数value
  2. 配置在对应的接口上
  3. 定义一个切面类,指定切点
  4. 在切入的方法体里写上权限判断的逻辑

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

  1. 只要配置了任何角色,就可以访问
  2. 有某个权限就可以访问
  3. 放行所有请求
  4. 只有超级管理员角色才可以访问
  5. 只有登录后才可以访问
  6. 在指定时间段内可以访问
  7. 有某个角色的情况下才可以访问
  8. 同时具有指定的多个角色情况下才可以访问

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

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

2 SpEL表达式

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

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

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

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

3 开搞

自定义注解

当然,万变不离其宗,自定义注解我们还是需要滴。这里呢,我们仅需要定义一个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();
  }
  throw new SecureException(ResultCode.REQ_REJECT);
 }

    private boolean handleAuth(ProceedingJoinPoint point) {
        //TODO 逻辑判断,返回true or false
    }

4 权限校验

关键点来了。这里我们要引入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);
  }
  return false;
 }
 /**
  * 获取方法上的参数
  *
  * @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    // 读取方法参数
   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() {
  return true;
 }

 /**
  * 只有超管角色才可访问
  *
  * @return {boolean}
  */

 public boolean denyAll() {
  return hasRole(RoleConstant.ADMIN);
 }

 /**
  * 是否已授权
  *
  * @return {boolean}
  */

 public boolean hasAuth() {
  if(Func.isEmpty(AuthUtil.getUser())){
   // TODO 返回异常提醒
  }else{
   return true;
  }
 }

 /**
  * 是否有时间授权
  *
  * @param start 开始时间
  * @param end   结束时间
  * @return {boolean}
  */

 public boolean hasTimeAuth(Integer start, Integer end)






请到「今天看啥」查看全文


推荐文章
鲁中晨报  ·  淄博这些人,不必再谈“白”色变!
11 小时前
鲁中晨报  ·  雷军,一度登顶中国首富!
昨天
鲁中晨报  ·  突发坠机!已致46人遇难
2 天前
阿尔法工场研究院  ·  LV中国贪腐案细节:“服务费”转入虚构公司
2 天前
禅语心苑  ·  人生歪理,歪得很有道理
8 年前
百读社  ·  红楼梦——关于安全感
7 年前
经典人生感悟  ·  写给自己的情书
7 年前