Block Image

Dopo l'articolo
OpenID Connect, OAuth2 e authorization code con Spring Cloud Gateway e WSO2 Identity Server,
alcuni di voi mi hanno chiesto come utilizzare un altro prodotto di WSO2, ovvero WSO2 API Manager, invece di Spring Cloud Gateway.
Nonostante la doc ufficiale di WSO2 spieghi la procedura per effettuare questa integrazione, trovo quest'ultima non facilissima da attuale, quindi ho deciso di scrivere un articolo a riguardo.

Perché integrare WSO2IS in WSO2AM

Nonostante WSO2AM possa gestire la sicurezza con OAuth2 (ma non solo), delegare a un Identity Server questo aspetto può portare dei vantaggi.
Innanzitutto, un Identity Server può gestire più aspetti sulla sicurezza, essendo creato per quello scopo.
Inoltre, se un giorno volessimo ad esempio utilizzare Spring Cloud Gateway invece di WSO2AM, lo switch sarebbe molto più leggero, in quanto stiamo andando a sostituire solo la tecnologia che si occupa di aspetti sul Gateway, mantenendo inalterato il componente che gestisce la sicurezza.

WSO2IS come Key Manager di WSO2AM

Per far gestire la sicurezza a WSO2IS, dobbiamo dire a WSO2AM che vogliamo utilizzare l'Identity Server come Key Manager.
La doc ufficiale è questa:
https://apim.docs.wso2.com/en/latest/administer/key-managers/configure-wso2is-connector/.

Dobbiamo, in breve, creare un database condiviso tra i due prodotti di WSO2, e cambiare delle impostazioni sul file deployment.toml di entrambi i prodotti.

Block Image

Tuttavia, ti mostrerò una procedura più veloce!

Utilizzare il docker-compose ufficiale

WSO2 mette a disposizione anche un docker-compose che permette di creare automaticamente tre container:

  • un container di MySQL, che conterrà i database di WSO2IS, WSO2AM, compreso quello condiviso, con annessi script SQL per creare tabelle e utenze
  • un container per WSO2IS, con il file deployment.toml già modificato
  • un container per WSO2AM, con il file deployment.toml già modificato per assegnare WSO2IS come Key Manager.

Potete scaricare il docker-compose, con i file di config, dal repo ufficiale:
https://github.com/wso2/docker-apim.git.

In particolare, a noi interessa il docker-compose che si trova al path:
docker-apim/docker-compose/apim-is-as-km-with-analytics.

Comunque, ci sono delle piccole modifiche da fare che elencherò qui sotto.

Cambiamo le immagini di WSO2IS e WSO2AM

Andiamo al path docker-apim/docker-compose/apim-is-as-km-with-analytics/dockerfiles/apim.
Troveremo il seguente 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/

Dobbiamo sostituire il nome dell'immagine da docker.wso2.com/wso2am:4.0.0.0 a wso2/wso2am:4.0.0 (versione community di WSO2AM).

Facciamo lo stesso con WSO2IS. Andiamo al path docker-apim/docker-compose/apim-is-as-km-with-analytics/dockerfiles/is-as-km/, dove avremo il seguente 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/

Sostituiamo il nome dell'immagine da docker.wso2.com/wso2is:5.11.0.0 a wso2/wso2is:5.11.0.

Modifichiamo il file deployment.toml di WSO2AM

Modifichiamo il file:
docker-apim/docker-compose/apim-is-as-km-with-analytics/conf/apim/repository/conf/deployment.toml.
Dobbiamo cancellare (o commentare) le seguenti righe:

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

Questo permette di non abilitare il prodotto analytics, visto che non siamo interessati a quest'ultimo in questo articolo.
Dobbiamo inoltre decommentare la riga:

#[apim.oauth_config]

e aggiungere questa property alla sezione apim.oauth_config:

enable_outbound_auth_header = true

Questa property permette di trasferire l'Authorization Header anche ai servizi di downstream, come il microservizio spring-resource-server dell'articolo su Spring Cloud Gateway (questa property svolge un po' la funzione del filtro TokenRelay di 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

Pubblichiamo l'app spring-resource-server

Innanzitutto, avviamo il docker-compose andando al path docker-apim\docker-compose\apim-is-as-km-with-analytics ed eseguendo il comando docker-compose up --build.

Poi, avviamo l'applicazione spring-resource-server. Rispetto all'articolo precedente, ho integrato OpenAPI su quest'ultima in modo tale da importare il file yaml OpenAPI su WSO2AM.
Scarichiamo il file openAPI chiamando la seguente url:
http://localhost:8080/spring-resource-server/v3/API-docs.yaml.

Modificate l'URL del server inserendo il vostro IP locale, ad esempio a me è 192.168.1.9.
Facciamo questo perché inserendo localhost, WSO2AM risolverebbe quest'ultimo con l'IP del suo container.
Questo è ad esempio il mio file openAPI finale:

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

Ora accediamo alla console publisher all'indirizzo: https://localhost:9443/publisher/.
Clicchiamo su REST API:

Block Image

Clicchiamo poi su Import Open API, scegliamo OpenAPI File/Archive e poi clicchiamo su Browse File to Upload per caricare il file yaml:

Block Image

Clicchiamo su Next, scriviamo /spring-resource-server nella text field Context:

Block Image

Infine, clicchiamo su Create.
Per pubblicare l'API, dobbiamo scegliere un Business Plain e dobbiamo deployare una Revision.

Clicchiamo su Business Plain, nella schermata di Overview:

Block Image

Scegliamo un'opzione qualsiasi, come ad esempio Unlimited e infine clicchiamo su Save.
Andiamo alla schermata Deployments sulla sinistra e poi clicchiamo su Deploy:

Block Image

Andiamo alla schermata di Overview e clicchiamo su Publish:

Block Image

Creiamo una Application in Developer Portal

Dal browser, andiamo su https://localhost:9443/devportal/, clicchiamo su Applications in alto e poi su Add New Application:

Block Image

Qui in pratica stiamo creando il Service Provider in WSO2IS. Diamo un nome all'applicazione e clicchiamo su Save:

Block Image

Andiamo ora alla schermata OAuth2 Tokens nella sezione Production Keys e clicchiamo su GENERATE KEYS.
Abilitiamo anche Code tra i Grant Type, inseriamo una Callback URL e infine clicchiamo su UPDATE.

Block Image

La creazione di una Application e l'associazione di WSO2IS come Key Manager, provocherà la creazione di un Service Provider all'interno di WSO2IS. Se infatti andiamo sulla console di carbon di WSO2IS (https://localhost:9444/carbon/), alla schermata Service Providers vedremo:

Block Image

Sottoscriviamo le API di spring-resource-server all'applicazione creata

Clicchiamo in alto su APIs e poi su SpringOAuth2

Block Image

Andiamo poi alla schermata Subscriptions e sottoscriviamo le API cliccando sul tasto SUBSCRIBE.

Block Image

Proviamo l'app spring-resource-server

In questo esempio, utilizzeremo il flusso OAuth2 con grant type password.
In produzione, evita di utilizzare questo grant type (per saperne di più: https://oauth.net/2/grant-types/password/).
Utilizziamo questo flusso perché le redirect in questo esempio non funzionerebbero bene, poiché stiamo accedendo a WSO2IS tramite tunnelling (localhost:4444 -> is-as-km:9443).
Per testare con un Client REST il flusso authorization code, dopo aver settato gli endpoint di WSO2IS in modo giusto, puoi utilizzare questa guida:
https://apim.docs.wso2.com/en/latest/design/API-security/oauth2/grant-types/authorization-code-grant/.

Torniamo a noi! Eseguiamo la chiamata https://localhost:9444/oauth2/token.
Utilizziamo la basic authentication passando come username il clientId e come password il clientSecret:

Block Image

Nella Request Body, settiamo i parametri grant_type=password, username=admin e password=admin:

Block Image

Eseguiamo la chiamata, dovremmo avere una risposta simile:

Block Image

La cURL relativa alla richiesta precedente, è la seguente:
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'

Possiamo prendere l'access token ricevuto e fare la seguente chiamata:
http://localhost:8280/spring-resource-server/v1
mettendo nell'Header il token Bearer. Ecco un esempio di cURL della richiesta:

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

Il servizio dovrebbe rispondere con 200 OK e col messaggio Hello World and admin.

Conclusioni e considerazioni

In questo articolo abbiamo visto come utilizzare il prodotto WSO2 API Manager invece di Spring Cloud Gateway.
Tuttavia, come avrai notato, il flusso di autenticazione non è identico. Qui l'OAuth2 Client è il client REST, cioè il FE.
Il FE ha la responsabilità di richiedere e gestire il token. Con Spring Cloud Gateway, il FE non è a conoscenza del token. La gestione del token lato backend permette di avere maggiore sicurezza, visto che il JWT non viene esposto all'esterno.
Ovviamente, non è sempre possibile evitare la gestione del token lato FE, dipende dai vari casi d'uso.
Se il FE è una Single Page Application, potrebbe essere una buona scelta avere un backend come OAuth2 Client (vedi flusso dell'articolo precedente).
Per app mobile, potrebbe essere una buona scelta far dialogare queste ultime dirittamente con l'Authorization Server, scegliendo il flusso con Authorization Code + PKCE.

Potete trovare il docker-compose e i file di configurazioni di WSO2 già aggiornati come descritti nell'articolo, sul mio repository GitHub:
https://github.com/vincenzo-racca/wso2am-is-as-km

Riferimenti