Introduction: The previous article has talked about the permission series and gateway implementation of micro-service, which are isolated. This article will integrate the back-end service with the gateway and the permission system. The implementation of the security authority part also explains the way to implement based on pre-validation, but because of the close connection with business, there is no specific example. Business permissions are closely related to business, and this integration project will verify the operation permissions of this part based on specific business services.
1. Previous review and integrated design
In the design and implementation of authentication authentication and API permission control in micro-service architecture series of articles, explains the authentication and authorization of Auth system in micro-service architecture. In the micro service gateway, the micro service gateway based on Netflix-Zuul component is explained. Let’s take a look at the architecture diagram of this integration.
Microservice architecture permission
The whole process is divided into two categories:
- The user is not logged in. The client (web and mobile) initiates the login request, and the gateway directly forwards the login request to the Auth service. The Auth service verifies the user’s identity information (the integration project omits the user system, and the readers can implement it by themselves, directly returning the user information by hard coding), and finally returns the legitimate token to the client.
- The user has logged in to request another service. In this case, when the client request reaches the gateway, the gateway will call the Auth system to verify the legitimacy of the request. If the authentication fails, it will directly reject the request and return 401. If the user passes the authentication, it is forwarded to a specific service. The service passes the filter and obtains the security permission information of the user based on the userId in the request header. Proceed validates the permissions required by the interface using the aspect; otherwise 403 is returned.
The first type is actually relatively simple. The design and implementation of authentication and API permission control in the micro-service architecture are basically realized. Now what we need to do is to combine with the gateway. In the second category, we create a back-end service that integrates with the gateway and Auth systems.
The following three services involved in the integration project are introduced respectively. The realization of gateway and Auth service has been talked about, this paper mainly talks about the integration of these two services need to change, and is to explain the main implementation of back-end services.
2. The gateway implementation
The implementation of microservice gateway has been basically introduced, including service routing and several filtering methods. This section will focus on practical integration. For the need to modify the enhancement of the following:
- Distinguish between exposed interfaces (that is, direct external access) and interfaces that require legitimate login
- The exposed interface is directly allowed and forwarded to specific services, such as login and token refresh
- The interface that needs to be accessed after logging in with a legal identity, according to the incoming Access token to construct the head, the head mainly includes userId and other information, can be set in the AuTH service according to their actual business.
- Last but not least, introduce the resource server configuration of Spring Security. For the exposed interface set permitAll(), the other interfaces enter the process of identity verification, call the Auth service, if passed, continue to forward normally, otherwise throw an exception, return 401.
The flow chart is as follows:
Gateway Routing Flowchart
2.1 permitAll implementation
Exposed interfaces can be accessed directly, depending on configuration files, which can be dynamically updated through the configuration center, so you don’t have to worry about hard-code. Define the path that requires Permitall in the configuration file.
auth:
permitall:
-
pattern: /login/**
-
pattern: /web/public/**Copy the code
When the service starts, the corresponding Configuration is read. The following Configuration properties read the Configuration starting with Auth.
@Bean
@ConfigurationProperties(prefix = "auth")
public PermitAllUrlProperties getPermitAllUrlProperties() {
return new PermitAllUrlProperties();
}Copy the code
Of course, you also need to have the entity class corresponding to PermitAllUrlProperties, which is relatively simple and will not be listed.
2.2 Strengthening the head
Filter Filter, which is the most practical Servlet technology, Web developers through Filter technology, Web server management of all Web resources for interception. Here, Filter is used to enhance the header, parse the token in the request, and construct unified header information. When it comes to specific services, userId in the header can be used to obtain and judge operation permissions.
public class HeaderEnhanceFilter implements Filter { //... @Autowired private PermitAllUrlProperties permitAllUrlProperties; @override public void init(FilterConfig FilterConfig) throws ServletException {} // The main filtering method @override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String authorization = ((HttpServletRequest) servletRequest).getHeader("Authorization"); String requestURI = ((HttpServletRequest) servletRequest).getRequestURI(); // test if request url is permit all , then remove authorization from header LOGGER.info(String.format("Enhance request URI : %s.", requestURI)); If (isPermitAllUrl(requestURI) &&isNoToAuthEndpoint (requestURI)) {// Remove headers, But does not include the login endpoint head it resetRequest = removeValueFromRequestHeader (servletRequest) (it); filterChain.doFilter(resetRequest, servletResponse); return; } // Check whether the header meets the specification if (stringutils. isNotEmpty(authorization)) {if (isJwtBearerToken(authorization)) {try {authorization = StringUtils.substringBetween(authorization, "."); String decoded = new String(Base64.decodeBase64(authorization)); Map properties = new ObjectMapper().readValue(decoded, Map.class); // Resolve tokens in authorization. Construct USER_ID_IN_HEADER String userId = (String) properties.get(SecurityConstants.USER_ID_IN_HEADER); RequestContext.getCurrentContext().addZuulRequestHeader(SecurityConstants.USER_ID_IN_HEADER, userId); } catch (Exception e) { LOGGER.error("Failed to customize header for the request", e); }}} else {// To fit, Set the anonymous head RequestContext. GetCurrentContext (.) addZuulRequestHeader (SecurityConstants. USER_ID_IN_HEADER ANONYMOUS_USER_ID); } filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } //... }Copy the code
The above code lists the basic processing flow of header enhancement, passing the request of isPermitAllUrl directly, otherwise determining whether the header meets the specification, and then parsing the tokens in authorization to construct USER_ID_IN_HEADER. Finally, an anonymous header is set for adaptation. Note that HeaderEnhanceFilter is also registered. Spring provides the FilterRegistrationBean class, which provides a setOrder method that sets sorting values for the filters so that Spring sorts web Filters before registering them and then registers them.
2.3 Configuring resource Servers
The configuration of resource server is used to control which exposed endpoints do not need to carry out identity validity verification, direct routing and forwarding, and which need to carry out identity loadAuthentication and call auth service.
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { //... // Set request pattern for permitAll. Override public void configure(HttpSecurity HTTP) throws Exception {http.csrf().disable() .requestMatchers().antMatchers("/**") .and() .authorizeRequests() .antMatchers(permitAllUrlProperties.getPermitallPatterns()).permitAll() .anyRequest().authenticated(); } / / through custom CustomRemoteTokenServices, Implanted identity legitimacy related validation @ Override public void the configure (ResourceServerSecurityConfigurer resources) throws the Exception { CustomRemoteTokenServices resourceServerTokenServices = new CustomRemoteTokenServices(); / /... resources.tokenServices(resourceServerTokenServices); }}Copy the code
The configuration of the resource server we see the author before the article should be very familiar, but here repeated. About ResourceServerSecurityConfigurer configuration class, as has been said before safety series, ResourceServerTokenServices interface, at that time, we also use the, You just use the default DefaultTokenServices. Side through custom CustomRemoteTokenServices, embedded identity legitimacy related validation.
Of course, this configuration also introduces the corresponding Spring Cloud Security OAuth2 dependencies.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>Copy the code
2.4 Custom implementation of RemoteTokenServices
One of the implementation is RemoteTokenServices ResourceServerTokenServices interface.
Queries the /check_token endpoint to obtain the contents of an access token.
If the endpoint returns a 400 response, this indicates that the token is invalid.
RemoteTokenServices queries the /check_token endpoint of the Auth service to obtain a token check result. If there is an error, the token is not valid. The author here CustomRemoteTokenServices implementation is used the way of thinking. It should be noted that the author’s project is based on Spring Cloud and Auth service is multi-instance, so Netflix Ribbon is used to obtain Auth service for load balancing. Spring Cloud Security adds the following default configuration, corresponding to the corresponding endpoint in the Auth service.
security:
oauth2:
client:
accessTokenUri: /oauth/token
clientId: gateway
clientSecret: gateway
resource:
userInfoUri: /user
token-info-uri: /oauth/check_tokenCopy the code
As for the specific CustomRemoteTokenServices implementation, can refer to the above ideas and RemoteTokenServices, is very simple, it.
At this point, the enhancement of the gateway service is complete. Let’s look at the implementation of the Auth service and back-end service. Again, why does information such as the userId passed in the header need to be constructed in the gateway? Readers can think about it, combined with safety and other aspects, 😆 the author temporarily do not give the answer.
3. The auth integration
The integration and modification of Auth service is actually not so much. The definition and relationship between user, role and permission has not been implemented before. The SQL statement of this part has been in Auth.SQL. Therefore, in order to provide a complete example, the author supplemented this part, mainly including the definition and implementation of corresponding interfaces of user-role, role and role-permission, and the implementation of adding, deleting, modifying and checking.
If readers want to refer to the integration project for practical application, this part can be completely enhanced according to their own business, including the creation of token, its customized information can also be unified processing in the gateway, constructed and passed to the back-end service.
The interfaces on this side are only listed, the other interfaces are not written (due to laziness..).
These two interfaces are also used by backend projects to obtain the corresponding userId permissions.
@requestMapping (method = requestmethod. GET, value = "/ API /userPermissions? userId={userId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) List<Permission> getUserPermissions(@RequestParam("userId") String userId); AccessLevel = accessLevel = accessLevel = accessLevel = accessLevel = accessLevel = accessLevel = accessLevel @RequestMapping(method = RequestMethod.GET, value = "/api/userAccesses? userId={userId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) List<UserAccess> getUserAccessList(@RequestParam("userId") String userId);Copy the code
Ok, this implementation is done, see the implementation in the project.
4. Backend project implementation
This section describes how to implement an example of Backend. What are the main functions of backend projects? Let’s consider the previous preparation for the gateway service and auth service:
- The header of the gateway construct, userId (and possibly other information, for example here), can be obtained in Backend
- Requests forwarded to the Backend service are authenticated or are directly exposed interfaces
- The auth service provides an interface for obtaining permissions based on the userId
Based on these, the author draws a general flow chart for Backend:
The flow chart of backend
The above flowchart is pretty clear, starting with the filter to populate the context of the SecurityContextHolder. Secondly, annotations are implemented through the section, whether it is necessary to enter the section expression processing. Execute the methods in the interface if you don’t need them. Otherwise, parse the required permissions in the annotation and check whether you have the permission to execute the annotations. If yes, continue executing the annotations. Otherwise, return 403 forbidden.
4.1 Filter
The Filter, as used by the gateway above, intercepts the client’s HttpServletRequest.
public class AuthorizationFilter implements Filter { @Autowired private FeignAuthClient feignAuthClient; @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain FilterChain) throws IOException, ServletException {logger.info(" Filter executing..." ); // pass the request along the filter chain String userId = ((HttpServletRequest) servletRequest).getHeader(SecurityConstants.USER_ID_IN_HEADER); if (StringUtils.isNotEmpty(userId)) { UserContext userContext = new UserContext(UUID.fromString(userId)); userContext.setAccessType(AccessType.ACCESS_TYPE_NORMAL); List<Permission> permissionList = feignAuthClient.getUserPermissions(userId); List<SimpleGrantedAuthority> authorityList = new ArrayList<>(); for (Permission permission : permissionList) { SimpleGrantedAuthority authority = new SimpleGrantedAuthority(); authority.setAuthority(permission.getPermission()); authorityList.add(authority); } CustomAuthentication userAuth = new CustomAuthentication(); userAuth.setAuthorities(authorityList); userContext.setAuthorities(authorityList); userContext.setAuthentication(userAuth); SecurityContextHolder.setContext(userContext); } filterChain.doFilter(servletRequest, servletResponse); } / /... }Copy the code
The above code mainly realizes that, according to the userId in the request header, the feign Client is used to obtain the set of permissions that the user in the Auth service has. A UserContext is then constructed. The UserContext is custom, implementing the Spring Security UserDetails, SecurityContext interface.
4.2 Implement @preauth annotations through facets
For spring-based projects, it is convenient to implement annotations using the AOP aspect of Spring. Here we use the custom annotation @preauth
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuth {
String value();
}Copy the code
Target is used to describe the scope of the annotation. If the annotation fails to compile outside the scope, it can be used for methods or classes. Takes effect at runtime. If you don’t know about annotations, Google them.
@Component @Aspect public class AuthAspect { @Pointcut("@annotation(com.blueskykong.auth.demo.annotation.PreAuth)") Private void cut() {} private void cut() {** * * * @param joinPoint * @param preAuth */ @around ("cut()&&@annotation(preAuth)") public Object Parameter Description Parameter Description Value (joinPoint joinPoint, PreAuth PreAuth) throws Throwable {// Take the expression String value = preauth.value (); / / Spring EL to parse the value SecurityExpressionOperations operations = new CustomerSecurityExpressionRoot(SecurityContextHolder.getContext().getAuthentication()); StandardEvaluationContext operationContext = new StandardEvaluationContext(operations); ExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression(value); Boolean result = expression.getValue(operationContext, boolea.class); If (result) {// Proceed with the method in the interface return joinPoint.proceed(); } return "Forbidden"; }}Copy the code
Because aspects apply to beans, add the class to the container with Component first. The @pointcut defines the annotation to intercept. @around Customizes a circular notification that can be injected directly into an annotation when you want to retrieve the attribute inside the annotation. Aspect mainly implements the expression, the value to make use of Spring EL parsing, will SecurityContextHolder. GetContext () is converted into standard operating context, then the analytic expressions of annotations, finally get the result of the expression to judge.
public class CustomerSecurityExpressionRoot extends SecurityExpressionRoot { public CustomerSecurityExpressionRoot(Authentication authentication) { super(authentication); }}Copy the code
CustomerSecurityExpressionRoot SecurityExpressionRoot inherited abstract class, and we use the real expression is defined in SecurityExpressionOperations interface, SecurityExpressionRoot and SecurityExpressionOperations interface are realized. However, Spring Security also calls Spring EL.
4.3 the controller interface
Let’s take a look at how the final interface uses the annotations implemented above.
@requestMapping (value = "/test", method = requestmethod.get) @preauth ("hasAuthority('CREATE_COMPANY')") HasRole ('Admin') public String test() {return "ok"; }Copy the code
@ PreAuth, a lot of expression can be defined, we can see SecurityExpressionOperations methods on the interface. So far I’ve only implemented the hasAuthority() expression, but if you want to support all the other expressions, you just need to construct the corresponding SecurityContextHolder.
4.4 Why is it designed like this?
Some readers may look at the above design and wonder why such a complex utility class was introduced, given that so many Spring Security utility classes are used.
Actually very simple, first, because enough SecurityExpressionOperations interface defined in the expression, and a more reasonable, can cover most in use at ordinary times our scene; Secondly, the author’s previous design is to specify the required permissions directly in the annotation, no extensibility, and readable; Finally, Spring Security 4 does introduce @preauthorize, @postauthorize and other annotations. I originally intended to use them, but I tried them myself, and found that it is not suitable for verifying the operation permissions at the interface level of microservice architecture. More than ten filters are too complicated. In addition, it also involves the Principal, Credentials and other information, which have been verified in the Auth system. The author thinks that the function implementation here is not very complicated and requires a very lightweight implementation. Readers who are interested can try to package the implementation of this part into JAR packages or Spring Boot starter.
5. To summarize
As mentioned above, the integration design concept is introduced first, which consists of three services: Gateway, Auth, and Backend Demo. The integrated project is generally complex, among which the Gateway service expands a lot of content. The exposed interface is routed and forwarded. The Starter of Spring Security is introduced here to configure the resource server to release the exposed path. For other interfaces, you need to invoke the Auth service for identity validity verification to ensure that requests to Backend are legitimate or public interfaces. Auth service on the basis of the previous, added role, Permission, user corresponding interface, for external call; Backend Demo is a new service that implements interface-level verification of operation permissions, mainly using custom annotations and Spring AOP aspects.
Due to the details of the implementation is a bit too much, this article is limited to space, only some of the important implementation listed and explained. If the reader is interested in practical applications, the information can be amplified based on the actual business, such as auth authorized tokens, header information for gateway interception request constructs, annotation supported expressions, and so on.
Of course, there are still a lot of places that can be optimized. If there is any unreasonable design in the integration project, you can give your suggestions.
Recommended reading
- Microservices gateway Netflix-Zuul
- Design and Implementation of Authentication and API Authority Control in Microservices Architecture (PART 1)
- Design and Implementation of Authentication and API Authority Control in Microservice Architecture (PART II)
- Design and Implementation of Authentication and API Authority Control in Microservice Architecture (PART III)
- Design and Implementation of Authentication and API Authority Control in Microservice Architecture (IV)
The source code
Auth: GitHub:github.com/keets2012/A… Yards cloud: gitee.com/keets/Auth-…
Gateway and Backend Dmeo: GitHub: github.com/keets2012/S… Yards cloud: gitee.com/keets/sprin…