Springboot 中引入 SpEL,复杂权限控制轻松搞定,非常优雅!

简介: Springboot 中引入 SpEL,复杂权限控制轻松搞定,非常优雅!

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

前言

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


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


配置在对应的接口上


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


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


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


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


有某个权限就可以访问


放行所有请求


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


只有登录后才可以访问


在指定时间段内可以访问


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


同时具有指定的多个角色情况下才可以访问傻眼了不,按照上面的实现逻辑的话怎么搞?加注解?写各种判断?这时候,其实我们就可以通过SpEL表达式来帮我们处理这个问题。 2.png


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



相关文章
|
NoSQL Java Maven
SpringBoot基础系列@Value 之字面量及 SpEL使用知识点介绍篇
承接上一篇博文【SpringBoot 基础系列】@Value 中哪些你不知道的知识点 中提及到但没有细说的知识点,这一篇博文将来看一下@Value除了绑定配置文件中的属性配置之外,另外支持的两种姿势
227 0
SpringBoot基础系列@Value 之字面量及 SpEL使用知识点介绍篇
|
JSON 安全 Java
SpringBoot基础系列之AOP结合SpEL实现日志输出中两点注意事项
使用 AOP 来打印日志大家一把都很熟悉了,最近在使用的过程中,发现了几个有意思的问题,一个是 SpEL 的解析,一个是参数的 JSON 格式输出
427 0
SpringBoot基础系列之AOP结合SpEL实现日志输出中两点注意事项
|
安全 Java 数据安全/隐私保护
【SpringBoot 基础系列】SpEL 语法扫盲与查询手册
Spring 表达式语言简称为 SpEL,一种类似 Ognl 的对象图导航语言(对于 ognl 不熟悉的同学可以参考一下: Ognl 系列博文) SeEL 为 Spring 提供了丰富的想象空间,除了一些基本的表达式操作之外,还支持
446 0
【SpringBoot 基础系列】SpEL 语法扫盲与查询手册
|
JSON 安全 前端开发
Springboot + Spring Security 前后端分离权限控制
最近因为领导要求,要把现在项目改成前端段分离的形式,因此,本文不再具体讲述Spring Security中如何实现用户动态权限认证以及带有图片验证码的自定义认证部分。所以适合对Security有一定了解的伙伴看一下。 看了一些帖子实现方式都一样,只是在于权限的认证不同而已,所以本文基于已经实现security登录认证及权限控制,主要针对登陆成功、登录失败、退出登录、未登录、无权访问、会话到期这几部分进行拦截处理并返回json字符串。 简单的说,只需要在security配置文件中,对应位置的跳转URL配置改为拦截处理而已,因此只需要有六个拦截实现类以及修改六处配置,就都OK了。
Springboot + Spring Security 前后端分离权限控制
|
算法 安全 前端开发
springboot整合shiro实现权限控制(下)
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。上个月写了一个在线教育的项目用到了shiro权限控制,这几天又复盘了一下,对其进行了深入探究,来总结一下。
118 0
springboot整合shiro实现权限控制(下)
|
缓存 安全 Java
springboot整合shiro实现权限控制(中)
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。上个月写了一个在线教育的项目用到了shiro权限控制,这几天又复盘了一下,对其进行了深入探究,来总结一下。
159 0
|
安全 前端开发 Java
springboot整合shiro实现权限控制(上)
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。上个月写了一个在线教育的项目用到了shiro权限控制,这几天又复盘了一下,对其进行了深入探究,来总结一下。
329 0
|
JSON 前端开发 JavaScript
Springboot + Vue + shiro 实现前后端分离、权限控制
本文总结自实习中对项目的重构。原先项目采用 Springboot+freemarker 模版,开发过程中觉得前端逻辑写的实在恶心,后端 Controller 层还必须返回 Freemarker 模版的 ModelAndView,逐渐有了前后端分离的想法,由于之前,没有接触过,主要参考的还是网上的一些博客教程等,初步完成了前后端分离,在此记录以备查阅。
|
安全 Java 数据库
SpringBoot权限控制
权限控制是一个比较重要的知识点。 先讲一下相关理论知识,如图: 每次发送请求都会调用到controller,而controller又会调用subject,每个用户对应一个subject(subject包含了session),且subject会负责和shiro交互,securityManager管理了Realm,而Realm可以进行登录验证,可以对用户操作付权限。
1511 0
|
1天前
|
Java Linux
Springboot 解决linux服务器下获取不到项目Resources下资源
Springboot 解决linux服务器下获取不到项目Resources下资源
http://www.vxiaotou.com