首页 > 其他分享 >SpringBoot引入SpEL,优雅控制复杂权限!

SpringBoot引入SpEL,优雅控制复杂权限!

时间:2024-01-19 23:32:03浏览次数:30  
标签:return SpringBoot SpEL boolean PreAuth 注解 权限 表达式

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

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

然而,在实际的开发中,对于权限校验的需求场景是很多的,比如:

SpringBoot引入SpEL,优雅控制复杂权限!_Express

傻眼了不,按照上面的实现逻辑的话怎么搞?

这时候,其实我们就可以通过SpEL表达式来帮我们处理这个问题。

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

不懂没关系,实践一下你就能明白他的作用了!!

自定义注解

当然,万变不离其宗,自定义注解我们还是需要滴。

这里呢,我们仅需要定义一个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
    }

权限校验

关键点来了。这里我们要引入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 < 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() {
  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) {
  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)) {
    return false;
   }
  }
  return true;
 }

 /**
  * 判断是否有该角色权限
  *
  * @param role 角色集合
  * @return {boolean}
  */
 public boolean hasAnyRole(String... role) {
        //获取当前登录用户
  BladeUser user = AuthUtil.getUser();
  if (user == null) {
   return false;
  }
  String userRole = user.getRoleName();
  if (StringUtil.isBlank(userRole)) {
   return false;
  }
  String[] roles = Func.toStrArray(userRole);
  for (String r : role) {
   if (CollectionUtil.contains(roles, r)) {
    return true;
   }
  }
  return false;
 }

}

实际使用

在使用的时候,我们只需要在类上或者接口上,加上@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的使用,让我们的权限配置校验更加灵活。

当出现新的场景时,我们仅需要在自定的表达式解析类中增加对应场景的解析方法即可。


最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!


标签:return,SpringBoot,SpEL,boolean,PreAuth,注解,权限,表达式
From: https://blog.51cto.com/u_16502039/9338009

相关文章

  • 【THM】Linux权限提升
    目录1、简介2、什么是权限提升“权限升级”是什么意思?为什么它如此重要?3、枚举hostname主机名Uname-a系统信息/proc/version进程、版本信息/etc/issueps命令env环境变量sudo-lls/etc/passwdhistoryifconfignetstatfind4、自动枚举工具5、权限提升:内核漏洞6、权限提升:Sudo......
  • Sql Server 创建用户并限制权限
    创建登录名使用sa或者Windows身份验证登录,【安全性】-【登录名】,右键【新建登录名】设置登录名属性设置数据库权限dbowner--拥有数据库全部权限,包括删除数据库权限dbaccessadmin--只给数据库用户创建其他数据库用户的权限,而没有创建登录用户的权限dbsec......
  • springboot项目中的一些小tips
    1.服务出现类不是java,左下角是红色"J"解决方式:选择java包右键,MarkDirectoryas=>SourcesRoot2.myBatisX插件推荐方便mapper和xml之间的对应,还可以生成代码,推荐文章:初步使用:https://blog.csdn.net/weixin_47025166/article/details/125362323进阶使用:https://baomidou.c......
  • SpringBoot使用Feign进行服务间通信
    一、前言在分布式系统中,服务间通信是非常常见的情况。Feign是一个开源的JavaHTTP客户端,可以帮助我们在SpringBoot应用中快速构建和使用HTTP客户端,方便实现服务间的通信。与其他HTTP客户端相比,Feign具有简化HTTPAPI定义、支持多种HTTP请求方法、支持请求和响应的压缩、支持请求和......
  • SpringBoot+dynamic-datasource实现多数据源(msyql、sqlserver、postgresql)手动切换
    场景SpringBoot+MybatisPlus+dynamic-datasources实现连接Postgresql和mysql多数据源:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/135653227上面实现通过注解和配置文件的方式去进行多数据源操作。如果业务需求,比如查询第三方接口时提供的是sqlserver的视图连......
  • SpringBoot集成邮件服务进行校验
    一、前言在我们进行注册、登录等操作的时候,为了保证用户信息的安全性,我们经常会需要接收短信验证码等场景,虽然它的安全系数较高,但是由于需要付费使用,所以我们也可以使用邮箱服务接收验证码来实现安全校验,提升系统安全系数。二、环境准备以QQ邮箱为例,我们需要在邮箱中开启SMTP服务获......
  • redis漏洞修复-使用redis普通用户启动redis服务程序(禁止root用户权限)
    2.Redis服务以root权限运行应用:Redis危险程度:高危风险描述:Redis服务以root权限运行,攻击者可通过Redis对服务器文件进行任意操作或者执行命令。通过查询命令获取敏感信息,通过写入公钥信息获取ssh登录权限等。如果Redis为root权限,攻击者则无需提权直接对服务器进行操作。验证信息......
  • 微服务、springboot热部署
    添加热部署依赖,如果项目中已有就不用加了<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency......
  • 基于 SpringBoot + magic-api + Vue3 + Element Plus + amis3.0 快速开发管理系统
    Tansci-Boot基于SpringBoot2+magic-api+Vue3+ElementPlus+amis3.0快速开发管理系统Tansci-Boot是一个前后端分离后台管理系统,前端集成amis低代码前端框架,后端集成magic-api的接口快速开发框架。包含基础权限、安全认证、以及常用的一些组件功能。项目......
  • springboot配置分页插件pageHelper和数据库方言的几种方式
    方式一:启动类配置分页插件(Application.java)1/**2*pageHelper分页插件3*/4@Bean5publicPageHelperByMyselfpageHelper(){6PageHelperByMyselfpageHelper=newPageHelperByMyself();7Propertiesproperties=newPr......