The previous chapter explained how to authenticate user permissions. User Authentication Under SpringCloud is a unified resource access control under microservices, which acts as a wall protecting the various business application services under the SpringCloud cluster. This chapter focuses on another layer of permission control: data permission, which means controlling the number of data resources that can be accessed.

Here’s an example:

A team 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 business data of the company, it cannot be grasped by all, so each salesman can only see the order data of his own city. Therefore, from the point of view of the system, each salesman has the function of accessing sales orders, and then need to configure the city that each salesman is responsible for, so as to screen the order data.

There are many ways to implement this functionality, and if similar requirements are needed in multiple parts of the system, we can make it a common feature. Here I present a relatively simple solution for your reference.

1. Overall structure

Data permission is in the form of an annotation hanging on every Controller that needs data permission control. Because it is related to specific program logic, it has certain invasions and needs to be used with the database.

Second, the implementation process

  1. The browserWith query permission range parametersaccessController, such ascities
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"}
Copy the code
  1. Intercept permission scope parameters with annotations and write back permission scope parameters that are within the authorization scope based on the pre-authorization scope comparison

    cities = ["cq"."cd"]
    Copy the code
  2. By transferring parameters to DAO layer, query conditions are assembled in SQL statement to realize data filtering

    select * from order where city in ('cq'.'cd')
    Copy the code

Three, implementation steps

1. Annotation implementation

For the full code of annotations, see the source code

1) Create annotations

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface ScopeAuth {

    String token(a) default "AUTH_TOKEN";
    String scope(a) default "";
    String[] scopes() default {};
}
Copy the code

This annotation is used by run-time retentionPolicy. RUNTIME on methods elementType. METHOD

Token: The token that identifies a unique user. It is related to the storage of user data permission

Scope, scopes: Scope of pre-requested data permissions

2) AOP implementation annotations

public class ScopeAuthAdvice {
  
    @Around("@annotation(scopeAuth)")
    public Object before(ProceedingJoinPoint thisJoinPoint, ScopeAuth scopeAuth) throws Throwable {
        / /... Omit the process
        / / access token
        String authToken = getToken(args, scopeAuth.token(), methodSignature.getMethod());
     		// Write back the range argument
        setScope(scopeAuth.scope(), methodSignature, args, authToken);
        
        return thisJoinPoint.proceed();
    }

    /** * Set range */
    private void setScope(String scope, MethodSignature methodSignature, Object[] args, String authToken) {
        // Get the request scope
        Set<String> requestScope = getRequestScope(args, scope, methodSignature.getMethod());
        ScopeAuthAdapter adapter = new ScopeAuthAdapter(supplier);
        // Authorized scope
        Set<String> authorizedScope = adapter.identifyPermissionScope(authToken, requestScope);
        // Write back to the new scope
        setRequestScope(args, scope, authorizedScope, methodSignature.getMethod());
    }

    /** * Write back request scope */
    private void setRequestScope(Object[] args, String scopeName, Collection<String> scopeValues, Method method) {
        // Parse the SPEL expression
        if (scopeName.indexOf(SPEL_FLAG) == 0) { ParseSPEL.setMethodValue(scopeName, scopeValues, method, args); }}}Copy the code

This is the demo code omit the process, the main function is to get the pre-authorized data range through token, and then do the intersection with the range of the request, and finally write back to the original parameter.

More SPEL expressions are used to calculate the result of the expression. For details, please refer to the ParseSPEL file

3) Calculation of intersection of permission scope

public class ScopeAuthAdapter {

    private final AuthQuerySupplier supplier;

    public ScopeAuthAdapter(AuthQuerySupplier supplier) {
        this.supplier = supplier;
    }

    /** * Verify permission scope *@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)) {
            // Return the request range if it is all open
            return requestScope;
        }

        if (requestScope == null) {
            return null;
        }

        if (requestScope.contains(USER_ALL)){
            // The scope of all permissions
            return authorizeScope;
        }

        // Remove different elements
        requestScope.retainAll(authorizeScope);

        returnrequestScope; }}Copy the code

There are two keyword ranges here for ease of setting

  • AUTH_ALL: preset all ranges, fully open meaning, set values for the database, request to pass what values
  • USER_ALL: Specifies the range of all permissions requested. If this value is passed when requested, the database default will prevail

4) Spring. factories automatically import class configurations

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

If the annotation function exists as a separate project, there may be problems when using it to find the import file. Classes that need to be initialized can be automatically loaded through this configuration file

2. Use of annotations

@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());
}
Copy the code

Add the @scopeauth annotation to controller methods that require data permissions

Scopes = {“# orderDto. cities”} : Run the final command to query the values of each input parameter orderDTO in each case

During actual development, you need to bring ** orderTo.getCities ()** into the subsequent logic and assemble this in SQL at the DAO layer to implement data filtering

3. Implement AuthStoreSupplier

The AuthStoreSupplier interface is the data permission storage interface. It can be used together with the AuthQuerySupplier based on the actual situation

This interface is not required and can be stored in the database or Redis (recommended). It is generally stored in Redis at the time of login

4. Implement AuthQuerySupplier

The AuthQuerySupplier interface is used to query data permission. You can query data according to the storage method. Redis is recommended

@Component
public class RedisAuthQuerySupplier implements AuthQuerySupplier {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /** * query scope */
    @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; }}}Copy the code

In a distributed architecture, this implementation can also be proposed to a permission module and further decoupled by remote invocation

5. Enable the data permission

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

Fourth, review

At this point the data permissions function is implemented. In order to reuse functionality in the microserver architecture, the creation of annotations and the implementation of AuthQuerySupplier are extracted into the common module, which makes it much easier to use in the concrete module. Simply add the @scopeauth annotation and configure the query method to use it.

V. Source code

The code in this article due to the length of reasons have some omission is not complete logic, if interested please Fork source code gitee.com/hypier/barr…