Micro-service Operation model
Build microservices based on spring Cloud and Netflix OSS-part 1
Build microservices based on spring Cloud and Netflix OSS, part 2
In this article, we will use OAuth 2.0 to create a security API for external access to the MicroServices completed by Part 1 and Part 2.
For more information about OAuth 2.0, you can access the introduction documentation: Parecki-oauth 2 Simplified and Jenkov-oauth 2.0 Tutorial, or canonical document IETF RFC 6749.
We will create a new microservices, named Product-api, as an external API (OAuth term for resource server-resource server) and exposed as a micro service through the previously introduced Edge server as token Relay, That is, the OAuth access token on the client side is forwarded to the resource server (Resource server). Also add OAuth Authorization server and an OAuth Client, which is the service consumer.
Continue to refine the system panorama of Part 2 and add a new OAuth component (identified as a red box):
We will demonstrate how the client side uses 4 standard authorization processes to obtain an access token from the authorization server (Authorization Server), and then use an access token to initiate secure access to the resource server, such as an API.
Note:
1/Protecting external APIs is not a special requirement for microservices, so 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 available for development and test environments. In practice, it needs to be replaced by an API platform, or a login or authorization process entrusted to Facebook or Twitter on social networks.
3/To reduce complexity, we purposely adopted the HTTP protocol. In real-world applications, OAuth communication requires the use of TLS, such as HTTPS to protect communication data.
4/In the previous article, in order to emphasize the diversity of microservices and monomer applications, each micro-service runs independently in a separate process.
1. Compiling the source code
As 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 the Windows platform is running, the corresponding bat file-build-all.bat is executed.
On the basis of Part 2, 2 new component sources were added, OAuth Authorization server, project name Auth-server, and OAuth Resource server. The project name is Product-api-service.
Compile output 10 log message:
BUILD Successful
2. Analyzing source code
See how 2 new components are implemented, and how Edge server updates and supports the delivery of OAuth access tokens. We also modify the URL of the API to make it easier to use.
2.1 Gradle Dependent
In order to use OAuth 2.0, we will introduce the Open source project: Spring-cloud-security and Spring-security-oauth2, adding 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
The implementation of the authorization server (Authorization server) is straightforward. You can use @enableauthorizationserver annotations directly. Next, use a configuration class to register the approved client-side application, specifying Client-id, Client-secret, and the allowed grant process and scope:
@EnableAuthorizationServer
Protected static class Oauth2config extends Authorizationserverconfigureradapter {
@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 suitable for developing and testing scenarios to simulate the registration process for client-side applications, in which OAuth Authorization Server, such as LinkedIn or GitHub, is used in practice.
Complete code to view the Authserverapplication.java.
Simulates the user registration of the identity provider in the real world (the OAuth term is called resource Owner) by adding a line of text to each user in the file application.properties, such as:
Security.user.password=password
Complete code to view the Application.properties file.
The implementation code also provides 2 simple Web user interfaces for user authentication and user approval, in which the source code can be viewed in detail:
https://github.com/callistaenterprise/blog-microservices/tree/B3/microservices/support/auth-server/src/main/ Resources/templates
2.3 Product-api-service
In order for the API code to implement OAuth Resource server functionality, we only need to add @enableoauth2resource annotations on the Main method:
@EnableOAuth2Resource
public class Productapiserviceapplication {
Complete code to view the Productapiserviceapplication.java.
The implementation of the API service code is similar to the implementation of the composite Service code in Part 2. To verify that OAuth is working correctly, we added the log output for User-id and access tokens:
@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 populate additional parameters, such as current user and authorization header.
2/For a more concise URL, we removed/product from the @requestmapping. When using Edge server, it automatically adds a/product prefix and routes the request to the correct service.
3/In real-world applications, it is not recommended to output an access token in log.
2.4 Update Edge Server
Finally, we need to have Edge server forward the OAuth access token to the API service. Fortunately, this is the default behavior and we don't have to do anything.
To make the URL more concise, we modified the routing configuration in Part 2:
Zuul:
Ignoredservices: "*"
Prefix:/api
Routes
PRODUCTAPI:/product/**
This way, you can use url:http://localhost:8765/api/product/123 instead of the url:http://localhost:8765/productapi/product/123 that you used earlier.
We also replaced the route to Composite-service for the route to Api-service.
Complete code to view the Application.yml file.
3. Start the system
Start RABBITMQ First:
$ ~/applications/rabbitmq_server-3.4.3/sbin/rabbitmq-server
As on the Windows platform, you need to confirm that the RABBITMQ service is started.
Then start the Infrastructure microservices service:
$ 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 Business Micro service:
$ 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 the Windows platform, you can perform the corresponding bat file-start-all.bat.
Once the microservices start to complete and register to the Service Discovery Server (Discovery server), the following logs are output:
Discoveryclient-Registration status:204
You are now ready to try to get an access token and use it to safely invoke the API interface.
4. Try 4 kinds of OAuth authorization process
The OAuth 2.0 specification defines 4 ways to grant access tokens:
For more information, see Jenkov-oauth 2.0 Authorization.
Note: Authorization Code and implicit are the most commonly used 2 ways. If not used in the previous 2 ways, the other 2 applies to a special scenario.
Next, look at how each grant process obtains an access token.
4.1 License Code Grant (Authorization)
First, we get a code license from the browser:
Http://localhost:9999/uaa/oauth/authorize? response_type=code& client_id=acme& redirect_uri=http://example.com& scope=webshop& state=97536
Log in First (User/password), then redirect to a URL similar to the following:
http://example.com/?
code=iyjh4y&
state=97536
Note: The state parameter is set to a random value in the request and is checked in response to avoid cross-site request forgery attack.
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 get an access token:
Curl Acme:[email protected]: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 an environment variable for subsequent access to the API:
token=eba6a974-3c33-48fb-9c2e-5978217ae727
Trying again to get the access token using the same code should fail. Because code is actually how a one-time password works.
Curl Acme:[email protected]: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 licensing (implicit Grant)
By implicit grant, you can skip the preceding code grant. Access tokens can be requested directly from the browser. Use the following URL address in your 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 verify by, 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 should be set to a random in the request so that it can be checked in response to avoid cross-site request forgery attack.
Save the access token in an environment variable for subsequent access to the API using:
token=00d182dc-9f41-41cd-b37e-59de8f882703
4.3 Resource owner password Credential license (Resource owner Password Credentials Grant)
In this scenario, the user does not have to access the Web browser, the user enters the credentials in the client side application, and obtains the access token from that credential (it is not a good idea from a security point of view if you do not trust the client side app):
Curl-s Acme:[email protected]: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 an environment variable so that you can use it when you subsequently access the API:
token=62ca1eb0-b2a1-4f66-bcf4-2c0171bbb593
4.4 Client-side credential license (client Credentials Grant)
In the last case, we assume that the user does not have permission to access the API. In this case, the client side application authenticates its own authorization server and obtains the access token:
Curl-s Acme:[email protected]: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 an environment variable so that you can use it when you subsequently access the API:
token=8265eee1-1309-4481-a734-24a2a4f19299
5. Accessing the API
Now that we've acquired the access token, we can start accessing the actual API.
The first attempt to access the API without acquiring an access token will fail:
Curl ' http://localhost:8765/api/product/123 '-s | JQ.
{
"Error": "Unauthorized",
"Error_description": "Full authentication are required to access this resource"
}
OK, this is in line with our expectations.
Next, we try to use an invalid access token and still fail:
Curl ' http://localhost:8765/api/product/123 ' \
-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 isn't configured to support it. "
}
Once again, the access request was rejected as expected.
Now, we try to execute the correct request using the access token returned by the licensing process:
Curl ' http://localhost:8765/api/product/123 ' \
-H "Authorization:bearer $TOKEN"-S | JQ.
{
"ProductId": 123,
"Name": "Name",
"Weight": 123,
"Recommendations": [...],
"Reviews": [...]
}
OK, it's working fine!
You can view the log records for the Api-service (product-api-service) output.
2015-04-23 18:39:59.014 Info 79321---[XNIO-2 task-20] o.s.c.s.o.r.userinfotokenservices:getting user INFO fro M:http://localhost:9999/uaa/user
2015-04-23 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
2015-04-23 18:39:59.381 INFO 79321---[ctApiService-10] s.c.m.a.p.service.productapiservice:getproductcomposite H ttp-status:200
We see the API contact Authorization Server, get the user information, and print out the user name and access token in log.
Finally, we try to invalidate the access token and impersonate it to expire. The impersonation can be performed by restarting Auth-server (which is stored in memory only) and then executing the previous request again:
Curl ' http://localhost:8765/api/product/123 ' \
-H "Authorization:bearer $TOKEN"-S | JQ.
{
"Error": "Access_denied",
"Error_description": "Unable to obtain a new access token for resource ' null '. The provider manager isn't configured to support it. "
}
As we expected, the previously acceptable access token is now rejected.
6. Summary
Thanks to the open source project spring-cloud-security and Spring-security-auth, we can easily set up the security API based on OAuth 2.0. Then, keep in mind that the authorization server we use is only available for development and test environments.
7. Next Step
In the subsequent articles, centralized log management will be implemented using the Elk technology stack (Elasticsearch, Logstash, and Kibana).
English original link:
Build microservices (Blog series-building microservices)
http://callistaenterprise.se/blogg/teknik/2015/05/20/blog-series-building-microservices/
Build MicroServices-Protect API interfaces with OAuth 2.0