Block Image

After
OpenID Connect, OAuth2 e authorization code con Spring Cloud Gateway e WSO2 Identity Server article,
some of you have asked me how to use another WSO2 product, namely WSO2 API Manager, instead of Spring Cloud Gateway.
Although the official WSO2 doc explains the procedure to make this integration, I find this last one not easy so I decided to write an article about it.

Why integrate WSO2IS into WSO2AM

Although WSO2AM can manage security with OAuth2 (but not only), delegating to an Identity Server this aspect can bring some advantages.
First of all, an Identity Server can handle more security aspects, being created for that purpose.
Also, if one day we wanted to use for example Spring Cloud Gateway instead of WSO2AM, the switch would be much more lighter, as we are going to replace only the technology that deals with aspects on the Gateway, keeping the component unchanged that handles security.

WSO2IS as Key Manager of WSO2AM

To have WSO2IS handle security, you need to tell WSO2AM that you want to use the Identity Server as the Key Manager.
The official doc is this:
https://apim.docs.wso2.com/en/latest/administer/key-managers/configure-wso2is-connector/.

You need, in short, to create a shared database between the two WSO2 products, and change some settings on the deployment.toml of both products.

Block Image

However, I'll show you a faster procedure!

Use the official docker-compose

WSO2 also provides a docker-compose that allows you to automatically create three containers:

  • a container of MySQL, which will contain the databases of WSO2IS, WSO2AM, including the shared one, with attached SQL scripts to create tables and users
  • a container for WSO2IS, with the deployment.toml file already modified
  • a container for WSO2AM, with the deployment.toml file already modified to assign WSO2IS as Key Manager.

You can download the docker-compose, with config files, from the official repo:
https://github.com/wso2/docker-apim.git.

Specifically, you are interested in the docker-compose located at path:
docker-apim/docker-compose/apim-is-as-km-with-analytics.

However, there are some minor tweaks that need to be made that I will list below.

Change the images of WSO2IS and WSO2AM

Go to the path docker-apim/docker-compose/apim-is-as-km-with-analytics/dockerfiles/apim_.
You will find the following Dockerfile:

FROM docker.wso2.com/wso2am:4.0.0.0
LABEL maintainer="WSO2 Docker Maintainers <dev@wso2.org>"

# build arguments for external artifacts
ARG MYSQL_CONNECTOR_VERSION=8.0.17

# add MySQL JDBC connector to server home as a third party library
ADD --chown=wso2carbon:wso2 https://repo1.maven.org/maven2/mysql/mysql-connector-java/${MYSQL_CONNECTOR_VERSION}/mysql-connector-java-${MYSQL_CONNECTOR_VERSION}.jar ${WSO2_SERVER_HOME}/repository/components/dropins/

You need to replace the name of the image from docker.wso2.com/wso2am:4.0.0.0 to wso2/wso2am:4.0.0 (WSO2AM community version).

Do the same with WSO2IS. Go to the path docker-apim/docker-compose/apim-is-as-km-with-analytics/dockerfiles/is-as-km/, where you have the follow Dockerfile:

FROM docker.wso2.com/wso2is:5.11.0.0
LABEL maintainer="WSO2 Docker Maintainers <dev@wso2.org>"

# build arguments for external artifacts
ARG MYSQL_CONNECTOR_VERSION=8.0.17

# add MySQL JDBC connector to server home as a third party library
ADD --chown=wso2carbon:wso2 https://repo1.maven.org/maven2/mysql/mysql-connector-java/${MYSQL_CONNECTOR_VERSION}/mysql-connector-java-${MYSQL_CONNECTOR_VERSION}.jar ${WSO2_SERVER_HOME}/repository/components/dropins/

# copy extensions to the identity server home
COPY dropins ${WSO2_SERVER_HOME}/repository/components/dropins/
# copy customized webapps to the identity server home
COPY webapps ${WSO2_SERVER_HOME}/repository/deployment/server/webapps/

Replace the image name from docker.wso2.com/wso2is:5.11.0.0 to wso2/wso2is:5.11.0.

Edit the WSO2AM deployment.toml file

Edit the file:
docker-apim/docker-compose/apim-is-as-km-with-analytics/conf/apim/repository/conf/deployment.toml.
You need to delete (or comment on) the following lines:

[apim.analytics]
enable = true
config_endpoint = "https://analytics-event-auth.choreo.dev/auth/v1"
auth_token = "<on-prem-key>"

This allows you to not enable the analytics product, since you are not interested in it in this article.
You also need to uncomment the line:

#[apim.oauth_config]

and add this property to the apim.oauth_config section:

enable_outbound_auth_header = true

This property allows to transfer the Authorization Header also to downstream services, like the microservice spring-resource-server microservice from the Spring Cloud Gateway article (this property is a bit like the TokenRelay filter of Spring Cloud Gateway).
Avremo quindi in definitiva:

[apim.oauth_config]
enable_outbound_auth_header = true
#auth_header = "Authorization"
#revoke_endpoint = "https://localhost:${https.nio.port}/revoke"
#enable_token_encryption = false
#enable_token_hashing = false

Publish the spring-resource-server app

First, start docker-compose by going to the path docker-apim\docker-compose\apim-is-as-km-with-analytics and running the command docker-compose up --build.

Then, start the spring-resource-server application. Compared to the previous article, I've integrated OpenAPI on this one in order to import the OpenAPI yaml file on WSO2AM.
Download the openAPI file by going to the following url:
http://localhost:8080/spring-resource-server/v3/API-docs.yaml.

Change the server URL by entering your local IP, for example to me it is 192.168.1.9.
You need to do this because by entering localhost, WSO2AM would resolve this to the IP of its container.
This is for example my final openAPI file:

openAPI: 3.0.1
info:
  title: SpringOAuth2
  version: v1
servers:
- url: http://192.168.1.9:8080/spring-resource-server
paths:
  /:
    get:
      tags:
      - hello-word-API
      operationId: welcome
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                type: string
      security:
      - bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

Now log in to the publisher console at:: https://localhost:9443/publisher/.
Click on REST API:

Block Image

Then click on Import Open API, choose OpenAPI File/Archive and then click on Browse File to Upload to upload the yaml file:

Block Image

Click Next, type /spring-resource-server in the Context text field:

Block Image

Finally, click on Create.
To publish the API, you must choose a Business Plain and you must deploy a Revision.

Click on Business Plain, on the Overview screen:

Block Image

Choose any option, such as Unlimited and finally click Save.
Go to the Deployments screen on the left and then click on Deploy:

Block Image

Go to the Overview screen and click on Publish:

Block Image

Create an Application in Developer Portal

From your browser, go to https://localhost:9443/devportal/, click on Applications at the top and then Add New Application:

Block Image

Here you are basically creating the Service Provider in WSO2IS. Give the application a name and click Save:

Block Image

Now go to the OAuth2 Tokens screen in the Production Keys section and click on GENERATE KEYS.
Enable also Code among the Grant Type, enter a Callback URL and finally click on UPDATE.

Block Image

The creation of an Application and the association of WSO2IS as Key Manager, will cause the creation of a Service Provider in WSO2IS. If in fact you go to the WSO2IS carbon console (https://localhost:9444/carbon/), at the Service Providers screen you will see:

Block Image

Subscribe spring-resource-server APIs to the created application

Click on APIs at the top and then on SpringOAuth2.

Block Image

Then go to the Subscriptions screen and subscribe to the APIs by clicking the SUBSCRIBE button.

Block Image

Try the spring-resource-server app

In this example, you will use the OAuth2 stream with grant type password.
In production, avoid using this grant type (read more: https://oauth.net/2/grant-types/password/).
Use this flow because the redirects in this example would not work well, since you are accessing WSO2IS via tunnelling (localhost:4444 -> is-as-km:9443).
To test with a REST Client the authorization code flow, after having set the WSO2IS endpoints in the right way, you can use this guide:
https://apim.docs.wso2.com/en/latest/design/API-security/oauth2/grant-types/authorization-code-grant/.

Back to us! Do the call https://localhost:9444/oauth2/token.
You use the basic authentication passing as username the clientId and as password the clientSecret:

Block Image

In the Request Body, set the parameters: grant_type=password, username=admin and password=admin:

Block Image

Do the call, you should get a similar response:

Block Image

The cURL related to the previous request, is as follows:
curl --location --request POST 'https://localhost:9444/oauth2/token' \ --header 'Authorization: Basic WEVKOTIyMG5HbnY3OVN4Uzc4YnMwRmVGT1I4YTp6dW1nRzJqQUlQMVpQUHdzYjU1S0V0aWJrMGNh' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'grant_type=password' \ --data-urlencode 'username=admin' \ --data-urlencode 'password=admin'

You can take the received access token and make the following call:
http://localhost:8280/spring-resource-server/v1
putting in the Header the Bearer token. Here is a cURL example of the request:

curl --location --request GET 'http://localhost:8280/spring-resource-server/v1' \
--header 'Authorization: Bearer eyJ4NXQiOiJNell4TW1Ga09HWXdNV0kwWldObU5EY3hOR1l3WW1NNFpUQTNNV0kyTkRBelpHUXpOR00wWkdSbE5qSmtPREZrWkRSaU9URmtNV0ZoTXpVMlpHVmxOZyIsImtpZCI6Ik16WXhNbUZrT0dZd01XSTBaV05tTkRjeE5HWXdZbU00WlRBM01XSTJOREF6WkdRek5HTTBaR1JsTmpKa09ERmtaRFJpT1RGa01XRmhNelUyWkdWbE5nX1JTMjU2IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJhZG1pbiIsImF1dCI6IkFQUExJQ0FUSU9OX1VTRVIiLCJhdWQiOiJ3cHI5VEJYczdBaWVHcTcxdWYxVDY4WndyOFlhIiwibmJmIjoxNjQ2NDgxNTIxLCJhenAiOiJ3cHI5VEJYczdBaWVHcTcxdWYxVDY4WndyOFlhIiwiaXNzIjoiaHR0cHM6XC9cL2xvY2FsaG9zdDo5NDQzXC9vYXV0aDJcL3Rva2VuIiwiZXhwIjoxNjQ2NDg1MTIxLCJpYXQiOjE2NDY0ODE1MjEsImp0aSI6IjFmODQxNzUwLThkMzgtNGRmNC04YjRkLTI0M2YyODJlYThkZCJ9.Igjo5V8efBY6qLiXtJNNwX2a5qb3d4miUi5jvOQZWjCubCLQ1nxBDNR6tVugB-DIgGagtssBPPaudh2PL1aWTiSC3vDzWR9PTIm99BEYrXGUmtgoEmdOIr65wfyT7t2eGrg2pEKbpbdK4lmFLkKAW9H3Wo6O-qFmKbdTIS0NaZ8knDwM3i_Bim4LjfSTZ9vJzuLjudPCBAqivd6W23UjnMS5eyg6x8eg6W2LWRFYEJE5f0JgiryasyPHCTF5si3LwNJ6EK_WJhH_k6sJ5Jx2ukR-fdI5hh2w1nxFVx9ywEbaNipPuOAppY_EIrE4vtOJR3kDo4BGDBYdFwYx2pgKfg'

The service should respond with 200 OK and the message Hello World and admin.

Conclusions and considerations

In this article you saw how to use the WSO2 API Manager product instead of Spring Cloud Gateway.
However, as you may have noticed, the authentication flow is not identical. Here the OAuth2 Client is the REST client, i.e. the FE.
The FE is responsible for requesting and managing the token. With Spring Cloud Gateway, the FE is not aware of the token. Managing the token on the backend side allows for more security since the JWT is not exposed to the outside world.
Of course, it is not always possible to avoid FE-side token management, it depends on the various use cases.
If the FE is a Single Page Application, it might be a good choice to have a backend like OAuth2 Client (see flow of the previous article).
For mobile apps, it could be a good choice to make them talk directly with the Authorization Server, choosing the flow with Authorization Code + PKCE.

You can find the docker-compose and the WSO2 configuration files already updated as described in the article, on my GitHub repository:
https://github.com/vincenzo-racca/wso2am-is-as-km

References