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
- 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"}
- 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"]
- 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)