SpringBoot: SpEL makes complex permission control easy!

83488fd63b97b8ec535b8 b76b98ede10.jpeg

The path to growth as a programmer

Internet/Programmer/Technology/Data Sharing

focus on

This article should take approximately 6 minutes to read.

From: 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

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.

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.

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.

Recommended reading:

You can always find new videos by scrolling through Douyin. How is this technology implemented?

Why does Alibaba not recommend using keySet() to traverse HashMap?

Interview questions for junior high school and senior Internet companies (9 Gs)
The content includes Java basics, JavaWeb, MySQL performance optimization, JVM, locks, millions of concurrency, message queues, high-performance caching, reflection, Spring family bucket principles, microservices, Zookeeper... and other technology stacks!
?Click to read the original text to get it! I have read it