Qualche mese fa mi sono occupato della realizzazione di un server OpenID Connect per gestire l'accesso a centinaia delle nostre applicazioni interne. Dalle nostre soluzioni personalizzate, adatte a piccole scale, siamo passati a uno standard riconosciuto. L'accesso attraverso un servizio centrale semplifica notevolmente le operazioni monotone, riduce i costi di implementazione delle autorizzazioni, consente di trovare molte soluzioni pronte e di non frammentare il cervello durante lo sviluppo di nuove. In questo articolo parlerò di questo passaggio e delle difficoltà che abbiamo incontrato.

Tanto tempo fa… Come è iniziato tutto
Qualche anno fa, quando le applicazioni interne erano diventate troppe per una gestione manuale, abbiamo scritto un'app per controllare gli accessi all'interno dell'azienda. Era una semplice applicazione Rails che si collegava a un database contenente informazioni sui dipendenti, dove venivano configurati gli accessi a diverse funzionalità. In quel periodo abbiamo avviato il primo SSO, che si basava sulla verifica dei token tra client e server di autorizzazione; il token veniva trasmesso in forma crittografata con vari parametri e controllato sul server di autorizzazione. Non era la soluzione più comoda, poiché per ogni applicazione interna dovevamo descrivere uno strato notevole di logica, e i database dei dipendenti non venivano affatto sincronizzati con il server di autorizzazione.
Dopo un po' di tempo, abbiamo deciso di semplificare il compito dell'autorizzazione centralizzata. Abbiamo trasferito il SSO su un bilanciatore. Con l'aiuto di OpenResty su Lua, abbiamo aggiunto un modello che controllava i token, sapeva a quale applicazione era destinata la richiesta e poteva verificare se c'era accesso. Questo approccio ha semplificato notevolmente il controllo degli accessi delle applicazioni interne: nel codice di ciascuna applicazione non era più necessario descrivere logica aggiuntiva. Alla fine abbiamo chiuso il traffico esterno, e l'applicazione stessa non sapeva nulla dell'autorizzazione.
Tuttavia, uno dei problemi è rimasto irrisolto. Cosa fare con le applicazioni che hanno bisogno di informazioni sui dipendenti? Si sarebbe potuto scrivere un'API per il servizio di autorizzazione, ma in tal caso sarebbe stata necessaria l'aggiunta di logica supplementare per ciascuna di queste applicazioni. Inoltre, volevamo liberarci dalla dipendenza da una nostra applicazione personalizzata, destinata in futuro a essere convertita in OpenSource, dal nostro server interno di autorizzazione. Di questo parleremo in un'altra occasione. La soluzione a entrambi i problemi è stata l'OAuth.
Standard comunemente accettati
L'OAuth è uno standard di autorizzazione chiaro e comunemente accettato, ma poiché le sue funzionalità da sole non sono sufficienti, si è iniziato a esaminare anche l'OpenID Connect (OIDC). L'OIDC è in sé la terza implementazione di uno standard di autenticazione aperto, che si è evoluto in un'estensione del protocollo OAuth 2.0 (protocollo di autorizzazione aperto). Questa soluzione risolve il problema della mancanza di dati sull'utente finale e consente anche di cambiare il fornitore di autorizzazione.
Tuttavia, non abbiamo scelto un fornitore specifico e abbiamo deciso di aggiungere integrazione con l'OIDC al nostro attuale server di autorizzazione. A favore di questa soluzione ha giocato il fatto che l'OIDC è molto flessibile in termini di autorizzazione per l'utente finale. Così è stata creata la possibilità di implementare il supporto per l'OIDC sul proprio attuale server di autorizzazione.

Il nostro percorso di implementazione di un server OIDC proprietario
1) Abbiamo modificato i dati nel formato richiesto
Per integrare l'OIDC è necessario convertire i dati attuali sugli utenti in un formato comprensibile dallo standard. In OIDC ciò viene chiamato Claims. I claims sono in sostanza i campi finali nel database degli utenti (nome, email, telefono, ecc.). Esiste , e tutto ciò che non rientra in questa lista è considerato personalizzato. Pertanto, il primo aspetto su cui prestare attenzione, se si desidera scegliere un fornitore OIDC esistente, è la possibilità di personalizzare facilmente nuovi claims.
Un gruppo di claims viene aggregato nel seguente sottoinsieme: Scope. Durante l'autorizzazione, viene richiesta l'accesso non a singoli claims, ma proprio agli scope, anche se parte dei claims dello scope non è necessaria.
2) Abbiamo implementato i grant necessari
La prossima parte dell'integrazione OIDC consiste nella scelta e nell'implementazione dei tipi di autorizzazione, noti come grant. Il tipo di grant scelto influirà sul successivo scenario di interazione dell'applicazione selezionata con il server di autorizzazione. Uno schema esemplificativo della scelta del grant appropriato è mostrato nell'immagine qui sotto.

Per la nostra prima applicazione abbiamo utilizzato il grant più comune: il Authorization Code. La sua particolarità rispetto ad altri è che si tratta di un processo in tre fasi, ovvero prevede un ulteriore controllo. Inizialmente, l'utente richiede il permesso di autorizzazione, riceve un token – l'Authorization Code, e poi, con questo token, come fosse un biglietto, richiede il token di accesso. Tutta l'interazione principale di questo scenario di autorizzazione si basa sui redirect tra l'applicazione e il server di autorizzazione. Maggiori informazioni su questo grant possono essere trovate .
OAuth sostiene il concetto che i token di accesso ottenuti dopo l'autorizzazione devono essere temporanei e dovrebbero essere cambiati idealmente ogni 10 minuti. Il grant Authorization Code è un controllo in tre fasi attraverso i redirect, e ripetere questo passaggio ogni 10 minuti, a dire il vero, non è l'attività più piacevole per gli occhi. Per risolvere questo problema esiste un altro grant: il Refresh Token, che abbiamo utilizzato anche noi. Qui è tutto più semplice. Durante il controllo con un altro grant, oltre al token di accesso principale viene rilasciato anche un altro token – il Refresh Token, che può essere utilizzato solo una volta e il cui periodo di validità è generalmente significativamente più lungo. Con questo Refresh Token, quando scade il TTL (Time to Live) del token di accesso principale, la richiesta di un nuovo token di accesso verrà inviata a un endpoint di un altro grant. Il Refresh Token utilizzato viene immediatamente annullato. Questo controllo è in due fasi e può essere eseguito in background, senza che l'utente se ne accorga.
3) Abbiamo configurato i formati di output dei dati dell'utente
Dopo che le sovvenzioni selezionate sono state implementate e l'autenticazione è attiva, è opportuno menzionare l'acquisizione dei dati sull'utente finale. In OIDC esiste un endpoint specifico per questo, dove con il proprio attuale token di accesso e se è ancora valido si possono richiedere i dati sugli utenti. E se i dati dell'utente non cambiano così frequentemente, ma è necessario consultare quelli attuali più volte, si può optare per una soluzione come i token JWT. Questi token sono anch'essi supportati dallo standard. Un token JWT consiste di tre parti: header (informazioni sul token), payload (eventuali dati necessari) e signature (firma, il token è firmato dal server e successivamente si può verificare l'origine della sua firma).
Nell'implementazione OIDC, il token JWT è chiamato id_token. Può essere richiesto insieme a un normale token di accesso e tutto ciò che rimane è verificare la firma. Per questo esiste un endpoint specifico sul server di autorizzazione con una coppia di chiavi pubbliche nel formato . Parlando di questo, vale la pena menzionare che c'è un altro endpoint che, basato sullo standard , riflette l'attuale configurazione del server OIDC. Contiene tutti gli indirizzi degli endpoint (incluso l'indirizzo della coppia di chiavi pubbliche utilizzate per la firma), i claim supportati e gli scope, gli algoritmi di crittografia utilizzati, i grant supportati, ecc.
Ad esempio in Google:
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"device_authorization_endpoint": "https://oauth2.googleapis.com/device/code",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"revocation_endpoint": "https://oauth2.googleapis.com/revoke",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token",
"none"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid",
"email",
"profile"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_secret_basic"
],
"claims_supported": [
"aud",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"iss",
"locale",
"name",
"picture",
"sub"
],
"code_challenge_methods_supported": [
"plain",
"S256"
],
"grant_types_supported": [
"authorization_code",
"refresh_token",
"urn:ietf:params:oauth:grant-type:device_code",
"urn:ietf:params:oauth:grant-type:jwt-bearer"
]
}In questo modo, tramite l'id_token, è possibile trasmettere tutte le rivendicazioni necessarie nel payload del token senza dover effettuare ogni volta una richiesta al server di autorizzazione per ottenere informazioni sull'utente. Lo svantaggio di questo approccio è che le informazioni dell'utente non vengono aggiornate immediatamente dal server, ma solo con un nuovo token di accesso.
Risultati dell'implementazione
Pertanto, dopo aver implementato un proprio server OIDC e configurato le connessioni ad esso sul lato delle applicazioni, abbiamo risolto il problema della trasmissione delle informazioni sugli utenti.
Poiché OIDC è uno standard aperto, abbiamo avuto la possibilità di scegliere un provider esistente o di implementare un server. Abbiamo provato Keycloak, che si è rivelato molto comodo da configurare; dopo aver impostato e modificato le configurazioni di connessione sul lato delle applicazioni, è pronto per l'uso. Sul lato delle applicazioni, rimane solo da cambiare le configurazioni di connessione.
Parlando delle soluzioni esistenti
All'interno della nostra organizzazione, come primo server OIDC, abbiamo creato la nostra implementazione, che è stata integrata man mano che si è reso necessario. Dopo un'attenta valutazione di altre soluzioni pronte, si può dire che si tratta di una questione controversa. A favore della scelta di implementare un proprio server ci sono le preoccupazioni dei fornitori riguardo all'assenza delle funzionalità necessarie, oltre alla presenza di un sistema obsoleto in cui erano presenti diverse autorizzazioni personalizzate per alcuni servizi e in cui erano già memorizzati molti dati sui dipendenti. Tuttavia, nelle implementazioni pronte, ci sono comodità per l'integrazione. Ad esempio, in Keycloak è presente un sistema di gestione degli utenti e i dati sono memorizzati direttamente in cui, trasferire i propri utenti lì non richiederà molto sforzo. A tal fine, in Keycloak è disponibile un'API che consentirà di completare tutte le azioni necessarie per il trasferimento.
Un altro esempio di implementazione certificata e interessante, a mio avviso, è Ory Hydra. È interessante perché composta da diversi componenti. Per l'integrazione avrai bisogno di collegare il tuo servizio di gestione utenti al loro servizio di autorizzazione ed espandere man mano che sarà necessario.
Keycloak e Ory Hydra non sono le uniche soluzioni pronte. È meglio scegliere un'implementazione certificata dalla OpenID Foundation. Di solito, tali soluzioni hanno il marchio di certificazione OpenID.

Non dimenticate i fornitori a pagamento esistenti, se non volete gestire il vostro server OIDC. Oggi ci sono molte buone opzioni.
E ora?
Nel prossimo futuro prevediamo di chiudere il traffico verso i servizi interni in un altro modo. Prevediamo di migrare il nostro attuale SSO su un bilanciatore utilizzando OpenResty su un proxy basato su OAuth. Qui esistono già molte soluzioni pronte, ad esempio:
Materiali aggiuntivi
– un buon servizio per verificare i token JWT
— un elenco di implementazioni OIDC certificate
Fonte: habr.com
