Introduction: This article is the first part of the “Design and Implementation of Authentication and API permission Control in microservices architecture” series, which is expected to explain the implementation of authentication and API permission control in microservices.
1. The background
Recently, I am working on the development of permission related services. After the system microservitization, the original single application is session-based security permission mode, which cannot meet the authentication and authentication requirements of the existing microservice architecture. In the micro-service architecture, an application is divided into several micro-applications. Each micro-application needs to authenticate access, and each micro-application needs to identify the current access user and their permissions. In particular, when the source of access is not only the browser, but also calls to other services, authentication in a single application architecture is not particularly appropriate. In the microservice architecture, multiple authentication scenarios, such as external application access, user-service authentication, and service-service authentication, need to be considered. For example, User A accesses the User Service. If User A does not log in to the Service, User A needs to log in to the Service and request for an authorization token. After obtaining the token, USER A carries the token to request access to A file. In this way, user A’s identity needs to be verified and user A can access the file. In order to adapt to the changes of architecture and requirements, the Auth permission module is isolated as a basic microservice system to provide services for other business services.
2. System architecture changes
From a single application architecture to a distributed architecture, simplified permissions change as shown in the following two figures. (1) Single application simplified version architecture diagram:
The advantage of distributed architecture, especially microservice architecture, is that business logic can be clearly divided and each microservice can assume a single function. After all, simpler things are more stable.
However, microservices also bring a lot of problems. For example, to complete a business operation, it is necessary to call across many microservices, so it is a challenge for us to control users’ calls to different microservices with the permission system. When the invocation of business microservices is connected to the permission system, their throughput should not be slowed down. When the permission system has problems, the progress of their business invocation should not be blocked, and of course the business logic should not be changed. The new business micro-service fast access permission system is relatively easy to control, so for the company’s existing micro-services, how to fast access without changing their architecture, for us, is also a big challenge.
3. Technical options
This mainly includes two aspects: one is authentication and authentication, authorization and legitimacy authentication of the requested user identity; The second is apI-level operation permission control. After the first point, after the authentication of the user’s identity is legitimate, the user can verify whether a specific request has the operation execution permission.
3.1 Authentication and Authentication
For the first requirement, I investigated some implementation options:
-
Distributed Session Scheme The distributed Session scheme stores user authentication information in shared storage and uses user sessions as keys to implement simple distributed hash mapping. When users access microservices, user data can be retrieved from shared storage. This works well in some scenarios where the user login status is opaque. It is also a highly available and scalable solution. The disadvantage of this solution is that shared storage requires some protection and therefore needs to be accessed through secure links, which is often quite complex to implement.
-
With the rise of Restful apis and microservices, token-based authentication is now more and more common. Tokens are different from Session ids in that they are not just keys. The Token usually contains user information. You can verify the identity by verifying the Token. The user enters login information and sends it to the authentication service for authentication. AuthorizationServer verifies whether the login information is correct and returns information such as basic user information, permission scope, and valid time, and the client storage interface. The user places the Token in the HTTP request header and invokes the relevant API. The invoked microservice verifies the Token. ResourceServer returns related resources and data.
The second scheme is adopted here. The benefits of OAuth2 Token authentication are as follows:
- Stateless server: The Token mechanism does not need to store session information on the server, because the Token itself contains information about all users.
- High performance, because the Token authentication does not need to access the database or remote services for permission verification, which naturally improves performance.
- A lot of applications today are both mobile and Web oriented,
OAuth2 Token
The mechanism can support mobile devices. - Last but not least, OAuth2 is used in conjunction with Spring Security. Spring Security OAuth2 is documented in more detail.
Oauth2 is divided into four modes according to different usage scenarios:
- Authorization Code
- Simplified patterns (Implicit)
- Password mode (Resource owner Password Credentials)
- Client credentials
For the above oAUTH2 four modes of unfamiliar students, you can baidu OAuth2, Ruan Yifeng’s article has an explanation. The password mode and client mode are commonly used.
3.2 Operation permission Control
For the second requirement, I focused on Spring Security and Shiro.
-
Shiro Shiro is a powerful and flexible open source security framework that handles authentication, authorization, management sessions, and password encryption very clearly. Shiro is easy to get started with a quick hand to control the granularity from rough to fine. With a high degree of freedom, Shiro can be used with Spring or alone.
-
The Spring Security community is very strong. Spring Security has all of Shiro’s features except that it can’t be separated from Spring. Spring Security also supports Oauth and OpenID, while Shiro needs to implement it manually. Spring Security has more granular permissions. But Spring Security is too complex.
Looking at the comments online, it seems that Shiro is in favor. The problem most people have with Spring Security is that it’s complicated and the documentation is too long. The author chose Spring Security after a comprehensive evaluation of complexity and the permission requirements to be implemented, as well as the results of the previous requirement survey.
4. System architecture
4.1 components
The final components of the Auth system are as follows:
OAuth2.0 JWT Token
Spring Security
Spring bootCopy the code
Step 4.2
The main steps are as follows:
- Configure the resource server and authentication server
- Configure Spring Security
The above steps are relatively general. The requirements mentioned in the previous section belong to the main content of Auth system, and the author will write another article to explain them later.
4.3 the endpoint
The endpoint provided:
/oauth/token? grant_type=password Request authorization token
/oauth/token? grant_type=refresh_token # the refresh token
/oauth/check_token # check token
/logout # Delete token and permission related informationCopy the code
4.4 the maven rely on
The main JAR package, pom.xml, is as follows:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
The < version > 2.2.0 < / version >
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
< version > 1.2.1 - the SNAPSHOT < / version >
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
< version > 1.2.1 - the SNAPSHOT < / version >
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
< version > 1.5.3. RELEASE < / version >
</dependency>Copy the code
4.5 AuthorizationServer configuration file
AuthorizationServer configuration overrides the following three methods for endpoints, Clients, and Security configurations.
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// Configure client authentication
clients.withClientDetails(clientDetailsService(dataSource));
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// Configure the token data source, customized token services and other information
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore(dataSource))
.tokenServices(authorizationServerTokenServices())
.accessTokenConverter(accessTokenConverter())
.exceptionTranslator(webResponseExceptionTranslator);
}Copy the code
4.6 ResourceServer configuration
The resource server configuration overrides the default configuration. In order to support the logout, custom a CustomLogoutHandler here and will return to HTTP status HttpStatusReturningLogoutSuccessHandler logoutSuccessHandler appointed.
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.requestMatchers().antMatchers("/ * *")
.and().authorizeRequests()
.antMatchers("/ * *").permitAll()
.anyRequest().authenticated()
.and().logout()
.logoutUrl("/logout")
.clearAuthentication(true)
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
.addLogoutHandler(customLogoutHandler());Copy the code
4.7 perform the endpoint
- The endpoint that obtains authorization is performed first.
method: post url: http://localhost:12000/oauth/token? grant_type=password header: { Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=, Content-Type: application/x-www-form-urlencoded } body: { username: keets, password: *** }Copy the code
The above constructs a POST request, which is written in detail. Username and password are provided by the client to the server to verify the user identity. The Authorization in the header is the encoded string stored in clientId and clientSecret. The result is as follows:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cC I6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOi Jmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ.5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo"."token_type": "bearer"."refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZX JfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZhLTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5Nj U1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoiZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb 4tTux6OQWouQJ2nn1DeE"."expires_in": 43195,
"scope": "all"."X-KEETS-UserId": "d6448c24-3c4c-4b80-8372-c2d61868f8c6"."jti": "bad72b19-d9f3-4902-affa-0430e7db79ed"."X-KEETS-ClientId": "frontend"
}Copy the code
After the user name and password are verified, the client receives the response message from the authorization server, including the Access token and refresh token. It also indicates that the token is a bearer with expiration time expires_in. I added custom info to the JWT token as UserId and ClientId.
2. The endpoint of authentication
method: post url: http://localhost:12000/oauth/check_token header: { Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=, Content-Type: application/x-www-form-urlencoded } body: { token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI 6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJ mcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ.5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo }Copy the code
The check_token request details are shown above. It should be noted that the author put the token just authorized in the body. There can be a variety of methods here, but there is no extension here.
{
"X-KEETS-UserId": "d6448c24-3c4c-4b80-8372-c2d61868f8c6"."user_name": "keets"."scope": [
"all"]."active": true."exp": 1508447756,
"X-KEETS-ClientId": "frontend"."jti": "bad72b19-d9f3-4902-affa-0430e7db79ed"."client_id": "frontend"
}Copy the code
After verifying the token is valid, the response shown above is returned. Basic information of corresponding tokens is also displayed in Response.
3. Refreshing the token The validity period of the token is not long, but the refresh period of the token is long. To avoid affecting user experience, you can use the refresh token to dynamically refresh the token.
method: post url: http://localhost:12000/oauth/token? grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMt NGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZXJfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZh LTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5NjU1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoi ZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb4tTux6OQWouQJ2nn1DeE
header:
{
Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=
}Copy the code
Its response is the same as /oauth/token, which is not listed here.
4. The cancellation token
method: get
url: http://localhost:9000/logout
header:
{
Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=
}Copy the code
If the logout succeeds, 200 is returned. The logout endpoint is used to empty the token and SecurityContextHolder.
5. To summarize
This article is the summary of a series of articles entitled “Design and Implementation of Authentication and API Permission Control in microservices Architecture”. It introduces the background of the project from the problems encountered. By investigating the existing technology and combining with the actual situation of the current project, the selection of technology is determined. Finally, the final implementation of the system is displayed. Behind will be from the implementation of the details, explain the implementation of the system. Stay tuned for future posts.
Welcome to follow my public number
reference
- Understand the 2.0
- Technical architecture for microservices API-level permissions
- Security authentication and authentication in the microservice architecture