Build microservices-use OAuth 2.0 to protect API interfaces and oauthapi
Microservice Operation Model
Build microservices-Part 1 based on Spring Cloud and Netflix OSS
Build microservices Based on Spring Cloud and Netflix OSS, Part 2
In this article, we will use OAuth 2.0 to create a secure API for external access to the microservices completed by Part 1 and Part 2.
For more information about OAuth 2.0, visit the introduction documents: Parecki-OAuth 2 Simplified and Jenkov-OAuth 2.0 Tutorial, or the specification documentation ietf rfc 6749.
We will create a new microservice named product-api as an external API (OAuth is called Resource Server-Resource Server ), the Edge Server is exposed as a microservice as a Token Relay, that is, the OAuth access Token of the Client is forwarded to the Resource Server ). In addition, the OAuth Authorization Server and an OAuth Client are added, that is, the service consumer.
Continue to improve the overall system diagram of Part 2 and add the new OAuth component (marked in red ):
We will demonstrate how to use four standard Authorization processes on the Client, obtain the Access Token from the Authorization Server, and then use the Access Token to initiate a secure Access to the resource Server, for example, API.
Note:
1. Protecting external APIS is not a special requirement of microservices. Therefore, this article applies to any architecture that uses OAuth 2.0 to protect external APIs;
2/The lightweight OAuth authorization system we use is only applicable to development and test environments. In practical applications, You need to replace it with an API platform, or delegate the login and authorization processes to Facebook or Twitter on social networks.
3/To reduce complexity, we have adopted the HTTP protocol. In practical applications, TLS is required for OAuth communication, such as HTTPS to protect communication data.
4/In the previous article, to emphasize the differences between microservices and single applications, each microservice runs independently in an independent process.
1. Compile the source code
Like in Part 2, we use Java SE 8, Git, and Gradle to access the source code and compile it:
Git clone https://github.com/callistaenterprise/blog-microservices.git
Cd blog-microservices
Git checkout-B B3 M3.1
/Build-all.sh
If you are running on a Windows platform, execute the appropriate bat file-build-all.bat.
On the basis of Part 2, two source code components are added: OAuth Authorization Server, project name auth-server, and OAuth Resource Server, project name product-api-service.
Compile and output 10 log messages:
BUILD SUCCESSFUL
2. analyze source code
View how two new components are implemented, and how Edge Server updates and supports passing OAuth access tokens. We will also modify the api url for ease of use.
2.1 GradleDependency
To use OAuth 2.0, we will introduce the open source project spring-cloud-security and spring-security-oauth2 to add the following dependencies.
Auth-server project:
Compile ("org. springframework. boot: spring-boot-starter-security ")
Compile ("org. springframework. security. oauth: spring-security-oauth2: 2.0.6.RELEASE ")
Complete code to view the auth-server/build. gradle file.
Product-api-service Project:
Compile ("org. springframework. cloud: spring-cloud-starter-security: 1.0.0.RELEASE ")
Compile ("org. springframework. security. oauth: spring-security-oauth2: 2.0.6.RELEASE ")
Complete code to view the product-api-service/build. gradle file.
2.2 AUTH-SERVER
Authorization Server is simple and straightforward. You can directly use the @ EnableAuthorizationServer annotation. Then, use a configuration class to register an approved Client application, specify the client-id, client-secret, and the allowed authorization process and scope:
@ EnableAuthorizationServer
Protected static class OAuth2Config extends authorizationserverassumeradapter {
@ Override
Public void configure (ClientDetailsServiceConfigurer clients) throws Exception {
Clients. inMemory ()
. WithClient ("acme ")
. Secret ("acmesecret ")
. AuthorizedGrantTypes ("authorization_code", "refresh_token", "implicit", "password", "client_credentials ")
. Scopes ("webshop ");
}
}
Obviously, this method is only applicable to simulating the registration process of Client applications in development and test scenarios. OAuth Authorization Server is used in actual applications, such as LinkedIn or GitHub.
For the complete code, you can view AuthserverApplication. java.
Simulate the user registration of Identity Provider in the real environment (OAuth is called Resource Owner). Add a line of text for each user in the application. properties file, for example:
Security. user. password = password
Complete code to view the application. properties file.
The implementation code also provides two simple web user interfaces for user authentication and user authorization. For details, you can view the source code:
Https://github.com/callistaenterprise/blog-microservices/tree/B3/microservices/support/auth-server/src/main/resources/templates
2.3 PRODUCT-API-SERVICE
To enable the API code to implement the OAuth Resource Server function, we only need to add @ EnableOAuth2Resource Annotation on the main method:
@ EnableOAuth2Resource
Public class ProductApiServiceApplication {
Complete code. You can view ProductApiServiceApplication. java.
The implementation of the API service code is similar to that of the combined service code in Part 2. To verify that OAuth works properly, we have added the log output of user-id and access token:
@ RequestMapping ("/{productId }")
@ HystrixCommand (fallbackMethod = "defaultProductComposite ")
Public ResponseEntity <String> getProductComposite (
@ PathVariable int productId,
@ RequestHeader (value = "Authorization") String authorizationHeader,
Principal currentUser ){
LOG.info ("ProductApi: User ={}, Auth ={}, called with productId = {}",
CurrentUser. getName (), authorizationHeader, productId );
...
Note:
1/Spring MVC will automatically fill in additional parameters, such as current user and authorization header.
2/To make the URL more concise, we remove/product from @ RequestMapping. When Edge Server is used, it automatically adds a/product prefix and routes requests to the correct service.
3. In actual applications, it is not recommended to output an access token in the log ).
2.4Update Edge Server
Finally, we need the Edge Server to forward the OAuth access token to the API service. Fortunately, this is the default action and we don't have to do anything.
To make the URL more concise, we modified the route configuration in Part 2:
Zuul:
IgnoredServices :"*"
Prefix:/api
Routes:
Productapi:/product /**
In this way, you can use URL: http: // localhost: 8765/api/product/123 instead of using the URL: http: // localhost: 8765/productapi/product/123.
We also replaced the route to composite-service with the route to api-service.
For the complete code, you can view the application. yml file.
3. Start the system
Start RabbitMQ first:
$ ~ // Applications/rabbitmq_server-3.4.3/sbin/rabbitmq-server
For example, on Windows, make sure that the RabbitMQ service has been started.
Start the infrastructure microservice:
$ Cd support/auth-server;./gradlew bootRun
$ Cd support/discovery-server;./gradlew bootRun
$ Cd support/edge-server;./gradlew bootRun
$ Cd support/monitor-dashboard;./gradlew bootRun
$ Cd support/turbine;./gradlew bootRun
Finally, start the service microservice:
$ Cd core/product-service;./gradlew bootRun
$ Cd core/recommendation-service;./gradlew bootRun
$ Cd core/review-service;./gradlew bootRun
$ Cd composite/product-composite-service;./gradlew bootRun
$ Cd api/product-api-service;./gradlew bootRun
As in Windows, you can execute the corresponding bat file-start-all.bat.
Once the microservice is started and registered to the Service Discovery Server, the following logs are output:
DiscoveryClient...-registration status: 204
Now you are ready to try to obtain the access token and use it to safely call the API interface.
4. Try four OAuth authorization procedures
The OAuth 2.0 specification defines four authorization methods to obtain an access token:
For more information, see Jenkov-OAuth 2.0 Authorization.
Note: Authorization Code and Implicit are the two most common methods. If the preceding two methods are not used, the other two are applicable to a special scenario.
Next, let's take a look at how each authorization process obtains an access token.
4.1 Authorization Code Grant)
First, we get a code license through a browser:
Http: // localhost: 9999/uaa/oauth/authorize? Response_type = code & client_id = acme & redirect_uri = http://example.com & scope = webshop & state = 97536
First Log On (user/password), and then redirect to a URL similar to the following:
Http://example.com /?
Code = IyJh4Y &
State = 97536
Note: Set the state parameter to a random value in the request and check in the response to avoid cross-site request forgery attacks.
Get the code parameter from the redirected URL and save it in the environment variable:
CODE = IyJh4Y
Now, as a secure web server, use code grant to obtain the access token:
Curl acme: acmesecret @ localhost: 9999/uaa/oauth/token \
-D grant_type = authorization_code \
-D client_id = acme \
-D redirect_uri = http://example.com \
-D code = $ CODE-s | jq.
{
"Access_token": "eba6a974-3c33-48fb-9c2e-5978217ae727 ",
"Token_type": "bearer ",
"Refresh_token": "0eebc878-145d-4df5-a1bc-69a7ef5a0bc3 ",
"Expires_in": 43105,
"Scope": "webshop"
}
Save the access token in the environment variable and use it for subsequent API access:
TOKEN = eba6a974-3c33-48fb-9c2e-5978217ae727
Attempt to use the same code to obtain the access token again should fail. Because the code is actually a one-time password.
Curl acme: acmesecret @ localhost: 9999/uaa/oauth/token \
-D grant_type = authorization_code \
-D client_id = acme \
-D redirect_uri = http://example.com \
-D code = $ CODE-s | jq.
{
"Error": "invalid_grant ",
"Error_description": "Invalid authorization code: IyJh4Y"
}
4.2 Implicit permission (Implicit Grant)
With Implicit Grant, you can skip the previous Code Grant. You can directly request an access token through a browser. Use the following URL in the browser:
Http: // localhost: 9999/uaa/oauth/authorize? Response_type = token & client_id = acme & redirect_uri = http://example.com & scope = webshop & state = 48532
Login (user/password) and authentication passed, the browser redirects to a URL similar to the following:
Http://example.com /#
Access_token = 00d182dc-9f41-41cd-b37e-59de8f882703 &
Token_type = bearer &
State = 48532 &
Expires_in= 42704
Note: The state parameter in the request should be set to a random one to check in the response to avoid cross-site request forgery attacks.
Save the access token in the environment variable for later use when accessing the API:
TOKEN = 00d182dc-9f41-41cd-b37e-59de8f882703
4.3 Resource Owner Password credential permission (Resource Owner Password Credentials Grant)
In this scenario, the user does not have to access the web browser. The user enters a credential in the Client application to obtain the access token through the credential (From the security perspective, if you do not trust Client applications, this is not a good solution ):
Curl-s acme: acmesecret @ localhost: 9999/uaa/oauth/token \
-D grant_type = password \
-D client_id = acme \
-D scope = webshop \
-D username = user \
-D password = password | jq.
{
"Access_token": "62ca1eb0-b2a1-4f66-bcf4-2c0171bbb593 ",
"Token_type": "bearer ",
"Refresh_token": "920fd8e6-1407-41cd-87ad-e7a07bd6337a ",
"Expires_in": 43173,
"Scope": "webshop"
}
Save the access token in the environment variable for later use when accessing the API:
TOKEN = 62ca1eb0-b2a1-4f66-bcf4-2c0171bbb593
4.4 Client Credentials Grant)
In the last case, we assume that the user can access the API without permission. In this case, the Client application verifies its own authorization server and obtains the access token:
Curl-s acme: acmesecret @ localhost: 9999/uaa/oauth/token \
-D grant_type = client_credentials \
-D scope = webshop | jq.
{
"Access_token": "8265eee1-1309-4481-a734-24a2a4f19299 ",
"Token_type": "bearer ",
"Expires_in": 43189,
"Scope": "webshop"
}
Save the access token in the environment variable for later use when accessing the API:
TOKEN = 8265eee1-1309-4481-a734-24a2a4f19299
5. access API
Now we have obtained the access token and can access the actual API.
First, the attempt to access the API will fail if the access token is not obtained:
Curl 'HTTP: // localhost: 8765/api/product/100'-s | jq.
{
"Error": "unauthorized ",
"Error_description": "Full authentication is required to access this resource"
}
OK. This meets our expectations.
Next, we try to use an invalid access token and it will still fail:
Curl 'HTTP: // localhost: 8765/api/product/8080 '\
-H "Authorization: Bearer invalid-access-token"-s | jq.
{
"Error": "access_denied ",
"Error_description": "Unable to obtain a new access token for resource 'null'. The provider manager is not configured to support it ."
}
Again, the access request was rejected as scheduled.
Now, we try to use the access token returned by the license process to execute the correct request:
Curl 'HTTP: // localhost: 8765/api/product/8080 '\
-H "Authorization: Bearer $ TOKEN"-s | jq.
{
"ProductId": 123,
"Name": "name ",
"Weight": 123,
"Recommendations": [...],
"Reviews": [...]
}
OK. This operation is normal!
You can view the log records output by api-service (product-api-service.
18:39:59. 014 INFO 79321 --- [XNIO-2 task-20] o. s. c. s. o. r. userInfoTokenServices: Getting user info from: http: // localhost: 9999/uaa/user
18:39:59. 030 INFO 79321 --- [ctApiService-10] s. c. m. a. p. service. ProductApiService: ProductApi: User = user, Auth = Bearer a0f91d9e-00a6-4b61-a59f-9a084936e474, called with productId = 123
18:39:59. 381 INFO 79321 --- [ctApiService-10] s. c. m. a. p. service. ProductApiService: GetProductComposite http-status: 200
We can see that the API contacts the Authorization Server to obtain user information and print the user name and access token in the log.
Finally, we try to invalidate the access token and simulate that it has expired. You can restart the auth-server (only storing this information in the memory) to simulate it, and then execute the preceding request again:
Curl 'HTTP: // localhost: 8765/api/product/8080 '\
-H "Authorization: Bearer $ TOKEN"-s | jq.
{
"Error": "access_denied ",
"Error_description": "Unable to obtain a new access token for resource 'null'. The provider manager is not configured to support it ."
}
As we expected, the previously accepted access token is now rejected.
6. Summary
Thanks to the spring-cloud-security and spring-security-auth open-source projects, we can easily set security APIs Based on OAuth 2.0. Remember that the Authorization Server we use is only applicable to development and test environments.
7. Next Step
In subsequent articles, ELK technology stacks (Elasticsearch, LogStash, and Kibana) will be used for centralized log management.
Link to the original English text:
Building Microservices)
Http://callistaenterprise.se/blogg/teknik/2015/05/20/blog-series-building-microservices/