Spring cloud microservice implements data permission control. The previous chapter describes how to verify user permissions

A group of salesmen follow up the national sales orders. They are divided by city. One salesman follows up the orders of three cities. In order to protect the company's business data from being mastered by everyone, each salesman can only see the order data of his own responsible city. Therefore, in terms of the system, each business representative has the function of accessing sales orders, and then it is necessary to configure the city in charge of each business representative to filter the order data.

There are many ways to realize this function. If similar requirements are required in many places in the system, we can put them forward to make a general function. Here I introduce a relatively simple solution for reference.

1, Overall architecture

image

Data permission is attached to each Controller that needs data permission control in the form of an annotation

Because it is related to the specific program logic, it has a certain invasiveness and needs to be used with the database.

2, Implementation process

image

  1. The browser passes the query permission range parameters to access the Controller, such as cities
POST http://127.0.0.1:8000/order/query
accept: */*
Content-Type: application/json
token: 1e2b2298-8274-4599-a26f-a799167cc82f
{"cities":["cq","cd","bj"],"userName":"string"}
  1. Intercept the permission range parameters through annotations, and write back the permission range parameters within the authorization range according to the comparison of pre authorization ranges
    cities = ["cq","cd"]
  1. Pass the parameters to the DAO layer, assemble the query conditions in the SQL statement, and realize data filtering
    select * from order where city in ('cq','cd')

3, Implementation steps

1. Annotation implementation

For the complete code of annotation, see the source code( https://links.jianshu.com/go?to=https%3A%2F%2Fgitee.com%2Fhypier%2Fbarry-

cloud%2Ftree%2Fmaster%2Fcloud-auth-logic)

1) Create annotation

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface ScopeAuth {
    String token() default "AUTH_TOKEN";
    String scope() default "";
    String[] scopes() default {};
}

This annotation is the annotation of ElementType.METHOD used by runtime RetentionPolicy.RUNTIME on the method

token: gets the ID identifying the unique user, which is related to the user data permission storage

scope, scopes: pre requested data permission range

2) AOP implementation annotation

public class ScopeAuthAdvice {
    @Around("@annotation(scopeAuth)")
    public Object before(ProceedingJoinPoint thisJoinPoint, ScopeAuth scopeAuth) throws Throwable {
        // ... omit the process
        // Get token
        String authToken = getToken(args, scopeAuth.token(), methodSignature.getMethod());
            // Writeback range parameter
        setScope(scopeAuth.scope(), methodSignature, args, authToken);
        return thisJoinPoint.proceed();
    }
    /**
     * set range
     */
    private void setScope(String scope, MethodSignature methodSignature, Object[] args, String authToken) {
        // Get request scope
        Set<String> requestScope = getRequestScope(args, scope, methodSignature.getMethod());
        ScopeAuthAdapter adapter = new ScopeAuthAdapter(supplier);
        // Authorized scope
        Set<String> authorizedScope = adapter.identifyPermissionScope(authToken, requestScope);
        // Writeback new range
        setRequestScope(args, scope, authorizedScope, methodSignature.getMethod());
    }
    /**
     * Write back request range
     */
    private void setRequestScope(Object[] args, String scopeName, Collection<String> scopeValues, Method method) {
        // Parsing SPEL expressions
        if (scopeName.indexOf(SPEL_FLAG) == 0) {
            ParseSPEL.setMethodValue(scopeName, scopeValues, method, args);
        }
    }
}

This is the demonstration code, and the process is omitted. The main function is to get the pre authorized data range through the token, intersect with the requested range, and finally write back the original parameters.

Many SPEL expressions are used to calculate the expression results. Please refer to the ParseSPEL file for details( https://links.jianshu.com/go?to=https%3A%2F%2Fgitee.com%2Fhypier%2Fbarry-

cloud%2Fblob%2Fmaster%2Fcloud-auth-

logic%2Fsrc%2Fmain%2Fjava%2Ffun%2Fbarryhome%2Fcloud%2Futil%2FParseSPEL.java)

3) Permission range intersection calculation

public class ScopeAuthAdapter {
    private final AuthQuerySupplier supplier;
    public ScopeAuthAdapter(AuthQuerySupplier supplier) {
        this.supplier = supplier;
    }
    /**
     * Verify the scope of authority
     * @param token
     * @param requestScope
     * @return
     */
    public Set<String> identifyPermissionScope(String token, Set<String> requestScope) {
        Set<String> authorizeScope = supplier.queryScope(token);
        String ALL_SCOPE = "AUTH_ALL";
        String USER_ALL = "USER_ALL";
        if (authorizeScope == null) {
            return null;
        }
        if (authorizeScope.contains(ALL_SCOPE)) {
            // If it is fully open, the request range is returned
            return requestScope;
        }
        if (requestScope == null) {
            return null;
        }
        if (requestScope.contains(USER_ALL)){
            // Scope of all authorizations
            return authorizeScope;
        }
        // Remove different elements
        requestScope.retainAll(authorizeScope);
        return requestScope;
    }
}

For convenience of setting, there are two keyword ranges

  • AUTH_ALL: all ranges are preset and fully open. Values are preset for the database. Any value requested is passed
  • USER_ALL: requests the scope of all authorizations. If this value is passed during the request, the default value of the database will prevail

4) spring.factories automatically import class configuration

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector=\
  fun.barryhome.cloud.annotation.ScopeAuthAdvice

If the annotation function exists as a separate project, there may be a problem that the import file cannot be found when using it. The class to be initialized can be automatically loaded through this configuration file

2. Use of notes

@ScopeAuth(scopes = {"#orderDTO.cities"}, token = "#request.getHeader(\"X-User-Name\")")
@PostMapping(value = "/query")
public String query(@RequestBody OrderDTO orderDTO, HttpServletRequest request) {
    return Arrays.toString(orderDTO.getCities());
}

Add @ ScopeAuth annotation on the controller method that needs to use data permission

scopes = {"#orderDTO.cities"}: indicates to take the cities value of the input parameter orderDTO, where the expression must be added

#

In the actual development process, orderDTO.getCities() needs to be brought into the subsequent logic and assembled in SQL at the DAO layer to realize the data filtering function

3. Implement AuthStoreSupplier

The AuthStoreSupplier interface is a storage interface for data permissions. It can be implemented according to the actual situation when used in conjunction with AuthQuerySupplier

This interface is unnecessary and can be stored by the database or Redis (recommended). It is generally saved in Redis while logging in

4. Implement AuthQuerySupplier

AuthQuerySupplier interface is a data permission query interface, which can be queried by storage method. Redis is recommended

@Component
public class RedisAuthQuerySupplier implements AuthQuerySupplier {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    /**
     * Query range
     */
    @Override
    public Set<String> queryScope(String key) {
        String AUTH_USER_KEY = "auth:logic:user:%s";
        String redisKey = String.format(AUTH_USER_KEY, key);
        List<String> range = redisTemplate.opsForList().range(redisKey, 0, -1);
        if (range != null) {
            return new HashSet<>(range);
        } else {
            return null;
        }
    }
}

In the distributed structure, this implementation can also be proposed to the permission module, which adopts the remote call mode to further decouple

5. Open Data permission

@EnableScopeAuth
@EnableDiscoveryClient
@SpringBootApplication
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

4, Overview

So far, the data permission function has been realized. In order to realize the reuse of functions in the micro server architecture, the creation of annotations and AuthQuerySupplier

If the implementation of is extracted into the public module, it is much easier to use the specific module. Just add the @ ScopeAuth annotation and configure the query method.

5, Source code

The code in the article is omitted due to length, which is not complete logic. If you are interested, please Fork the source code

(https://links.jianshu.com/go?to=https%3A%2F%2Fgitee.com%2Fhypier%2Fbarry-

cloud%2Ftree%2Fmaster%2Fcloud-auth-logic)

Posted by enoyhs on Sun, 21 Nov 2021 21:46:14 -0800