SpringBoot: SpEL makes complex permission control easy!

This is a community that may be useful to you

One-to-one communication/interview brochure/resume optimization/job search questions, welcome to join the “Yudao Rapid Development Platform” Knowledge Planet. The following is some information provided by Planet:

  • “Project Practice (Video)”: Learn from books, “practice” from past events

  • “Internet High Frequency Interview Questions”: Studying with your resume, spring blossoms

  • “Architecture x System Design”: Overcoming difficulties and mastering high-frequency interview scenario questions

  • “Advancing Java Learning Guide”: systematic learning, the mainstream technology stack of the Internet

  • “Must-read Java Source Code Column”: Know what it is and why it is so

320a0fbb807c90da669c23ea59bdebac.gif

This is an open source project that may be useful to you

Domestic Star is a 100,000+ open source project. The front-end includes management backend + WeChat applet, and the back-end supports monomer and microservice architecture.

Functions cover RBAC permissions, SaaS multi-tenancy, data permissions, mall, payment, workflow, large-screen reports, WeChat public account, etc.:

  • Boot address: https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • Cloud address: https://gitee.com/zhijiantianya/yudao-cloud

  • Video tutorial: https://doc.iocoder.cn

Source: juejin.cn/post/
7226674759626571833

  • 1 Introduction

  • 2 SpEL expression

  • 3 Let’s do it

    • Custom annotations

    • Define aspects

  • 4 Permission verification

    • First, introduce SpEL:

    • Then, get the expression we need from the annotation.

    • Expression parsing

    • Custom parsing method

  • 5 Practical use

  • 6 principles

  • 7 Summary

cb6e0ef928f4556d67a81a5c29bc8a5e.jpeg

What I bring to you this time is another design that is very common but hard to think of. That is, SpEL is introduced into permission control to make complex permission control simpler and more flexible.

1 Foreword

Everyone should be familiar with the use of custom annotations + aspects to control interface permissions in Springboot. There are also a large number of blogs to introduce the entire implementation process. The overall idea is as follows:

  1. Customize an annotation for permission verification, including the parameter value

  2. Configure on the corresponding interface

  3. Define an aspect class and specify the cut point

  4. Write the logic of permission judgment in the method body of the cut-in

At first glance, there is nothing wrong with it. I learned it, learned it~, and collected it. However, when I actually used it, I was dumbfounded. Why? In actual development, you will find that there are many demand scenarios for permission verification, such as:

  1. As long as any role is configured, you can access

  2. You can access it if you have certain permissions

  3. Release all requests

  4. Only super administrator roles can access

  5. Can only be accessed after logging in

  6. Accessible within a specified period of time

  7. You can only access it if you have a certain role

  8. You can only access it if you have multiple specified roles at the same time.

I’m dumbfounded. What should I do according to the above implementation logic? Add annotation? Write various judgments? At this time, we can actually use SpEL expressions to help us deal with this problem.

Backend management system + user applet implemented based on Spring Boot + MyBatis Plus + Vue & Element, supporting RBAC dynamic permissions, multi-tenancy, data permissions, workflow, three-party login, payment, SMS, mall and other functions

  • Project address: https://github.com/YunaiV/ruoyi-vue-pro

  • Video tutorial: https://doc.iocoder.cn/video/

2 SpEL expression

SpEL was mentioned earlier in this article, so what exactly is SpEL?

The full name of SpEL is Spring Expression Language, which is Spring expression language. It is provided by Spring 3.0. Its most powerful feature is the ability to assemble values into our properties or constructors via expressions that are executed at runtime.

If there are friends who have not been exposed to it before and do not understand the meaning of this sentence, then it does not matter, continue to read on, and you will understand its role through subsequent practice.

Backend management system + user applet implemented based on Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element, supporting RBAC dynamic permissions, multi-tenancy, data permissions, workflow, three-party login, payment, SMS, mall and other functions

  • Project address: https://github.com/YunaiV/yudao-cloud

  • Video tutorial: https://doc.iocoder.cn/video/

3 Let’s do it

Custom annotations

Of course, everything remains the same, and we still need custom annotations. Here, we only need to define a value attribute to receive the expression.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuth {

 /**
  *
  *
  * permissionAll()-----You can access as long as the role is configured
  * hasPermission("MENU.QUERY")-----roles with MENU.QUERY operation permission can access
  * permitAll()-----release all requests
  * denyAll()-----Only super administrator roles can access
  * hasAuth()-----can only be accessed after logging in
  * hasTimeAuth(1,,10)-----Only accessed between 1-10 o'clock
  * hasRole(Administrator’)-----Only those with administrator role can access
  * hasAllRole('Administrator','Chief Engineer')-----Only those with the roles of Administrator and Chief Engineer can access it
  *
  *Spring el
  * Document address: https://docs.spring.io/spring/docs/5.1.6.RELEASE/spring-framework-reference/core.html#expressions
  */
 String value();

}

Define aspects

After the annotations are defined, we need to define aspects. There is one point to consider here. What we hope is that if there are annotations on the method, restrictions will be placed on the method. If there are no annotations on the method and there are annotations on the class alone, then the permission annotations on the class will take effect on all interfaces under the class.

Therefore, if we cut to the point, we must use the @within annotation. code show as below:

@Around(
  "@annotation(PreAuth annotation path) || " +
   "@within(PreAuth annotation path)"
 )
 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 logical judgment, return true or false
    }

4 Permission verification

Here comes the key point. Here we are going to introduce SpEL.

First, introduce SpEL:

private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();

Then, get the expression we need from the annotation.

MethodSignature ms = point.getSignature() instanceof MethodSignature? (MethodSignature) point.getSignature():null;
  Method method = ms.getMethod();
  // Read permission annotation, priority method, if not, read the class
  PreAuth preAuth = ClassUtil.getAnnotation(method, PreAuth.class);
  // Judgment expression
  String condition = preAuth.value();
  if (StringUtil.isNotBlank(condition)) {
            //TODU expression parsing
        }

Expression parsing

private boolean handleAuth(ProceedingJoinPoint point) {
  MethodSignature ms = point.getSignature() instanceof MethodSignature? (MethodSignature) point.getSignature():null;
  Method method = ms.getMethod();
  // Read permission annotation, priority method, if not, read the class
  PreAuth preAuth = ClassUtil.getAnnotation(method, PreAuth.class);
  // Judgment expression
  String condition = preAuth.value();
  if (StringUtil.isNotBlank(condition)) {
   Expression expression = EXPRESSION_PARSER.parseExpression(condition);
   //Method parameter values
   Object[] args = point.getArgs();
   StandardEvaluationContext context = getEvaluationContext(method, args);
            //Get the results of analytical calculation
   return expression.getValue(context, Boolean.class);
  }
  return false;
 }
 /**
  * Get the parameters on the method
  *
  * @param method method
  * @param args variable
  * @return {SimpleEvaluationContext}
  */
private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) {
  // Initialize Sp el expression context and set AuthFun
  StandardEvaluationContext context = new StandardEvaluationContext(new AuthFun());
  // Set the expression to support spring beans
  context.setBeanResolver(new BeanFactoryResolver(applicationContext));
  for (int i = 0; i < args.length; i + + ) {
   //Read method parameters
   MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
   //Set method parameter names and values as spel variables
   context.setVariable(methodParam.getParameterName(), args[i]);
  }
  return context;
 }

Custom parsing method

After reading the above parsing and processing, are you very confused? You only see getting expressions, getting parameters, setting parameters, and then expression.getValue and that’s it. Some students will ask, what is the logic of your permission verification?

Don’t worry, the key point is here: StandardEvaluationContext context = new StandardEvaluationContext(new AuthFun()); You found it in the above code. This AuthFun is the object for our permission verification.

So, we still have to define this object. To carry out specific permission verification logic processing, each method defined here can be used as an expression in permission annotations. code show as below:

public class AuthFun {


 /**
  * Determine whether the role has interface permissions
  *
  * @return {boolean}
  */
 public boolean permissionAll() {
  //TODO
 }

 /**
  * Determine whether the role has interface permissions
  *
  * @param permission permission number, corresponding to MENU_CODE of the menu
  * @return {boolean}
  */
 public boolean hasPermission(String permission) {
  //TODO
 }

 /**
  * Release all requests
  *
  * @return {boolean}
  */
 public boolean permitAll() {
  return true;
 }

 /**
  * Only super management characters can access
  *
  * @return {boolean}
  */
 public boolean denyAll() {
  return hasRole(RoleConstant.ADMIN);
 }

 /**
  * Whether it has been authorized
  *
  * @return {boolean}
  */
 public boolean hasAuth() {
  if(Func.isEmpty(AuthUtil.getUser())){
   // TODO returns exception reminder
  }else{
   return true;
  }
 }

 /**
  * Is there time authorization?
  *
  * @param start start time
  * @param end end time
  * @return {boolean}
  */
 public boolean hasTimeAuth(Integer start, Integer end) {
  Integer hour = DateUtil.hour();
  return hour >= start & amp; & amp; hour <= end;
 }

 /**
  * Determine whether the role has permissions
  *
  * @param role single role
  * @return {boolean}
  */
 public boolean hasRole(String role) {
  return hasAnyRole(role);
 }

 /**
  * Determine whether you have all role permissions
  *
  * @param role role collection
  * @return {boolean}
  */
 public boolean hasAllRole(String... role) {
  for (String r : role) {
   if (!hasRole(r)) {
    return false;
   }
  }
  return true;
 }

 /**
  * Determine whether the role has permissions
  *
  * @param role role collection
  * @return {boolean}
  */
 public boolean hasAnyRole(String... role) {
        //Get the current logged in user
  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;
 }

}

5 Practical use

When using it, we only need to add @PreAuth directly to the class or interface. Pay attention when writing the value. The value should be the method and value defined in the AuthFun class. Parameters, if we define the parsing method hasAllRole(String... role), then in the annotation, we can write @PreAuth("hasAllRole('Role 1', 'Role 2')"), it should be noted that the parameters must be enclosed in single quotes.

@PreAuth("hasPermission('LM_QUERY,LM_QUERY_ALL')")
public T interface name....

6 Principles

It can be seen based on the actual usage above. SpEL expression parsing dynamically parses the string “hasAllRole('Role 1', 'Role 2')” in our annotation into hasAllRole(parameter 1, parameter 1) , and call the method with the same name in our registered class.

7 Summary

Through the use of SpEL, our permission configuration verification is more flexible. When a new scene appears, we only need to add the parsing method corresponding to the scene in the custom expression parsing class. Compared to the previous implementation, this has to be said to be a better choice.

Welcome to join my knowledge planet and comprehensively improve your technical capabilities.

To join, Long press” or “Scan” the QR code below:

ae2b57d497d0d1bdc888fcab39e1a763.png

Planet’s content includes: project practice, interviews and recruitment, source code analysis, and learning routes.

ef3c54176eb0e4607a791b9afe5508cc.png

1b9c878b808fce495cd261d0f0df3d52.png2a83ef5bf9d60ae70834ff32028f77c1.png 673d450b2039c381f132bbdec7fa53b7.pngdcfaecc6d26bccb8598dcc5315 56fe97.png

If the article is helpful, please read it and forward it.
Thank you for your support (*^__^*)