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.
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:
Clicchiamo poi su Import Open API, scegliamo OpenAPI File/Archive e poi clicchiamo su Browse File to Upload per caricare il file yaml:
Clicchiamo su Next, scriviamo /spring-resource-server nella text field Context:
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:
Scegliamo un'opzione qualsiasi, come ad esempio Unlimited e infine clicchiamo su Save.
Andiamo alla schermata Deployments sulla sinistra e poi clicchiamo su Deploy:
Andiamo alla schermata di Overview e clicchiamo su Publish:
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:
Qui in pratica stiamo creando il Service Provider in WSO2IS. Diamo un nome all'applicazione e clicchiamo su Save:
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.
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:
Sottoscriviamo le API di spring-resource-server all'applicazione creata
Clicchiamo in alto su APIs e poi su SpringOAuth2
Andiamo poi alla schermata Subscriptions e sottoscriviamo le API cliccando sul tasto SUBSCRIBE.
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:
Nella Request Body, settiamo i parametri grant_type=password, username=admin e password=admin:
Eseguiamo la chiamata, dovremmo avere una risposta simile:
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
- Mio articolo su flusso OIDC + OAuth2 con Spring Cloud Gateway:
https://www.vincenzoracca.com/blog/framework/spring/spring-oauth/. - WSO2IS come Key Manager:
https://apim.docs.wso2.com/en/latest/install-and-setup/setup/distributed-deployment/configuring-wso2-identity-server-as-a-key-manager/.