Autenticazione degli utenti con Go


Le app in esecuzione su piattaforme gestite da Google Cloud come App Engine possono evitare di gestire l'autenticazione utente e la gestione delle sessioni utilizzando Identity-Aware Proxy (IAP) per controllare l'accesso. IAP non solo può controllare l'accesso all'app, ma fornisce anche informazioni sugli utenti autenticati, inclusi l'indirizzo email e un identificatore persistente all'app sotto forma di nuove intestazioni HTTP.

Obiettivi

  • Richiedi agli utenti della tua app App Engine di autenticarsi mediante IAP.

  • Accedi alle identità degli utenti nell'app per visualizzare l'indirizzo email autenticato dell'utente corrente.

Costi

In questo documento vengono utilizzati i seguenti componenti fatturabili di Google Cloud:

Per generare una stima dei costi in base all'utilizzo previsto, utilizza il Calcolatore prezzi. I nuovi utenti di Google Cloud possono essere idonei a una prova senza costi aggiuntivi.

Una volta completate le attività descritte in questo documento, puoi evitare la fatturazione continua eliminando le risorse che hai creato. Per ulteriori informazioni, consulta la pagina Pulizia.

Prima di iniziare

  1. Accedi al tuo account Google Cloud. Se non conosci Google Cloud, crea un account per valutare le prestazioni dei nostri prodotti in scenari reali. I nuovi clienti ricevono anche 300 $di crediti gratuiti per l'esecuzione, il test e il deployment dei carichi di lavoro.
  2. Nella pagina del selettore di progetti della console Google Cloud, seleziona o crea un progetto Google Cloud.

    Vai al selettore progetti

  3. Installa Google Cloud CLI.
  4. Per initialize gcloud CLI, esegui questo comando:

    gcloud init
  5. Nella pagina del selettore di progetti della console Google Cloud, seleziona o crea un progetto Google Cloud.

    Vai al selettore progetti

  6. Installa Google Cloud CLI.
  7. Per initialize gcloud CLI, esegui questo comando:

    gcloud init
  8. Preparare l'ambiente di sviluppo.

Configurazione del progetto

  1. Nella finestra del terminale, clona il repository dell'app di esempio sulla tua macchina locale:

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git
  2. Passa alla directory che contiene il codice di esempio:

    cd golang-samples/getting-started/authenticating-users

Contesto

Questo tutorial utilizza IAP per l'autenticazione degli utenti. Questo è solo uno dei vari approcci possibili. Per saperne di più sui vari metodi di autenticazione degli utenti, consulta la sezione Concetti di autenticazione.

L'app Hello user-email-address

L'app utilizzata per questo tutorial è un'app Hello World App Engine minima, con una funzionalità non tipica: al posto di "Hello World", viene visualizzato "Hello user-email-address", dove user-email-address è l'indirizzo email dell'utente autenticato.

Questa funzionalità è possibile esaminando le informazioni autenticate che IAP aggiunge a ogni richiesta web che passa alla tua app. Sono state aggiunte tre nuove intestazioni di richiesta a ogni richiesta web che raggiunge la tua app. Le prime due intestazioni sono stringhe di testo normale che puoi utilizzare per identificare l'utente. La terza intestazione è un oggetto firmato con le stesse informazioni.

  • X-Goog-Authenticated-User-Email: l'indirizzo email di un utente li identifica. Non archiviare informazioni personali se la tua app può evitarle. Questa app non memorizza alcun dato, ma li restituisce solo all'utente.

  • X-Goog-Authenticated-User-Id: questo ID utente assegnato da Google non mostra informazioni sull'utente, ma consente a un'app di sapere che l'utente che ha eseguito l'accesso è lo stesso visto in precedenza.

  • X-Goog-Iap-Jwt-Assertion: puoi configurare le app Google Cloud in modo che accettino richieste web da altre app cloud, bypassando IAP, oltre alle richieste web da internet. Se un'app è configurata in questo modo, è possibile che queste richieste abbiano intestazioni falsificate. Anziché utilizzare una delle intestazioni di testo normale menzionate in precedenza, puoi utilizzare e verificare questa intestazione con firma crittografica per verificare che le informazioni siano state fornite da Google. Sia l'indirizzo email dell'utente che un ID utente permanente sono disponibili come parte di questa intestazione firmata.

Se sei sicuro che l'app sia configurata in modo tale che solo le richieste web internet possano raggiungerla e che nessuno possa disabilitare il servizio IAP per l'app, il recupero di un ID utente univoco richiede solo una singola riga di codice:

userID := r.Header.Get("X-Goog-Authenticated-User-ID")

Tuttavia, un'app resiliente dovrebbe aspettarsi che si verifichino problemi, inclusi problemi di configurazione imprevisti o ambientali, quindi consigliamo di creare una funzione che utilizzi e verifichi l'intestazione firmata in modo crittografico. La firma dell'intestazione non può essere contraffatta e, una volta verificata, può essere utilizzata per restituire l'identificazione.

Nozioni di base sul codice

Questa sezione spiega il funzionamento del codice. Se vuoi eseguire l'app, puoi vai alla sezione Eseguire il deployment dell'app.

  • Il file go.mod definisce un modulo Go e i moduli da cui dipende.

    module github.com/GoogleCloudPlatform/golang-samples/getting-started/authenticating-users
    
    go 1.19
    
    require (
    	cloud.google.com/go/compute/metadata v0.3.0
    	github.com/golang-jwt/jwt v3.2.2+incompatible
    )
    
  • Il file app.yaml indica ad App Engine quale ambiente linguistico richiede il tuo codice.

    runtime: go112
  • L'app inizia importando i pacchetti e definendo una funzione main. La funzione main registra un gestore di indice e avvia un server HTTP.

    
    // The authenticating-users program is a sample web server application that
    // extracts and verifies user identity data passed to it via Identity-Aware
    // Proxy.
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    	"time"
    
    	"cloud.google.com/go/compute/metadata"
    	"github.com/golang-jwt/jwt"
    )
    
    // app holds the Cloud IAP certificates and audience field for this app, which
    // are needed to verify authentication headers set by Cloud IAP.
    type app struct {
    	certs map[string]string
    	aud   string
    }
    
    func main() {
    	a, err := newApp()
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	http.HandleFunc("/", a.index)
    
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    		log.Printf("Defaulting to port %s", port)
    	}
    
    	log.Printf("Listening on port %s", port)
    	if err := http.ListenAndServe(":"+port, nil); err != nil {
    		log.Fatal(err)
    	}
    }
    
    // newApp creates a new app, returning an error if either the Cloud IAP
    // certificates or the app's audience field cannot be obtained.
    func newApp() (*app, error) {
    	certs, err := certificates()
    	if err != nil {
    		return nil, err
    	}
    
    	aud, err := audience()
    	if err != nil {
    		return nil, err
    	}
    
    	a := &app{
    		certs: certs,
    		aud:   aud,
    	}
    	return a, nil
    }
    
  • La funzione index recupera il valore dell'intestazione dell'asserzione JWT che IAP ha aggiunto dalla richiesta in entrata e chiama la funzione validateAssertion per convalidare il valore firmato in modo crittografico. L'indirizzo email viene quindi utilizzato in una risposta web minima.

    
    // index responds to requests with our greeting.
    func (a *app) index(w http.ResponseWriter, r *http.Request) {
    	if r.URL.Path != "/" {
    		http.NotFound(w, r)
    		return
    	}
    
    	assertion := r.Header.Get("X-Goog-IAP-JWT-Assertion")
    	if assertion == "" {
    		fmt.Fprintln(w, "No Cloud IAP header found.")
    		return
    	}
    	email, _, err := validateAssertion(assertion, a.certs, a.aud)
    	if err != nil {
    		log.Println(err)
    		fmt.Fprintln(w, "Could not validate assertion. Check app logs.")
    		return
    	}
    
    	fmt.Fprintf(w, "Hello %s\n", email)
    }
    
  • La funzione validateAssertion verifica che l'asserzione sia stata firmata correttamente e restituisce l'indirizzo email e l'ID utente associati.

    La convalida di un'asserzione JWT richiede la conoscenza dei certificati di chiave pubblica dell'entità che ha firmato l'asserzione (in questo caso Google) e del pubblico a cui è destinata l'asserzione. Per un'app App Engine, il segmento di pubblico è una stringa contenente informazioni di identificazione del progetto Google Cloud. La funzione validateAssertion recupera questi certificati dalla funzione certs e la stringa di pubblico dalla funzione audience.

    
    // validateAssertion validates assertion was signed by Google and returns the
    // associated email and userID.
    func validateAssertion(assertion string, certs map[string]string, aud string) (email string, userID string, err error) {
    	token, err := jwt.Parse(assertion, func(token *jwt.Token) (interface{}, error) {
    		keyID := token.Header["kid"].(string)
    
    		_, ok := token.Method.(*jwt.SigningMethodECDSA)
    		if !ok {
    			return nil, fmt.Errorf("unexpected signing method: %q", token.Header["alg"])
    		}
    
    		cert := certs[keyID]
    		return jwt.ParseECPublicKeyFromPEM([]byte(cert))
    	})
    
    	if err != nil {
    		return "", "", err
    	}
    
    	claims, ok := token.Claims.(jwt.MapClaims)
    	if !ok {
    		return "", "", fmt.Errorf("could not extract claims (%T): %+v", token.Claims, token.Claims)
    	}
    
    	if claims["aud"].(string) != aud {
    		return "", "", fmt.Errorf("mismatched audience. aud field %q does not match %q", claims["aud"], aud)
    	}
    	return claims["email"].(string), claims["sub"].(string), nil
    }
    
  • Puoi cercare l'ID numerico e il nome del progetto Google Cloud e inserirli personalmente nel codice sorgente, ma la funzione audience lo fa per te eseguendo una query sul servizio di metadati standard reso disponibile per ogni app App Engine. Poiché il servizio di metadati è esterno al codice dell'app, il risultato viene salvato in una variabile globale che viene restituita senza dover cercare i metadati nelle chiamate successive.

    Il servizio di metadati App Engine (e servizi di metadati simili per altri servizi di computing di Google Cloud) ha l'aspetto di un sito web e viene interrogato da query web standard. Tuttavia, il servizio di metadati non è in realtà un sito esterno, ma una funzionalità interna che restituisce le informazioni richieste sull'app in esecuzione, quindi è sicuro utilizzare http anziché le richieste https. Il servizio metadati viene utilizzato per ottenere gli identificatori Google Cloud correnti necessari per definire il pubblico di destinazione dell'asserzione JWT.

    
    // audience returns the expected audience value for this service.
    func audience() (string, error) {
    	projectNumber, err := metadata.NumericProjectID()
    	if err != nil {
    		return "", fmt.Errorf("metadata.NumericProjectID: %w", err)
    	}
    
    	projectID, err := metadata.ProjectID()
    	if err != nil {
    		return "", fmt.Errorf("metadata.ProjectID: %w", err)
    	}
    
    	return "/projects/" + projectNumber + "/apps/" + projectID, nil
    }
    
  • La verifica di una firma digitale richiede il certificato di chiave pubblica del firmatario. Google fornisce un sito web che restituisce tutti i certificati di chiave pubblica attualmente utilizzati. Questi risultati vengono memorizzati nella cache qualora siano di nuovo necessari nella stessa istanza dell'app.

    
    // certificates returns Cloud IAP's cryptographic public keys.
    func certificates() (map[string]string, error) {
    	const url = "https://www.gstatic.com/iap/verify/public_key"
    	client := http.Client{
    		Timeout: 5 * time.Second,
    	}
    	resp, err := client.Get(url)
    	if err != nil {
    		return nil, fmt.Errorf("Get: %w", err)
    	}
    
    	var certs map[string]string
    	dec := json.NewDecoder(resp.Body)
    	if err := dec.Decode(&certs); err != nil {
    		return nil, fmt.Errorf("Decode: %w", err)
    	}
    
    	return certs, nil
    }
    

Deployment dell'app

Ora puoi eseguire il deployment dell'app e quindi abilitare IAP per richiedere agli utenti di eseguire l'autenticazione prima di poter accedere all'app.

  1. Nella finestra del terminale, vai alla directory contenente il file app.yaml ed esegui il deployment dell'app in App Engine:

    gcloud app deploy
    
  2. Quando richiesto, seleziona un'area geografica vicina.

  3. Quando ti viene chiesto se vuoi continuare l'operazione di deployment, inserisci Y.

    In pochi minuti l'app sarà attiva su internet.

  4. Visualizza l'app:

    gcloud app browse
    

    Nell'output, copia web-site-url, l'indirizzo web dell'app.

  5. In una finestra del browser, incolla web-site-url per aprire l'app.

    Non viene visualizzata nessuna email perché non stai ancora utilizzando IAP e quindi non vengono inviate informazioni sull'utente all'app.

Abilita IAP

Ora che esiste un'istanza App Engine, puoi proteggerla con IAP:

  1. Nella console Google Cloud, vai alla pagina Identity-Aware Proxy.

    Vai alla pagina di Identity-Aware Proxy

  2. Poiché è la prima volta che attivi un'opzione di autenticazione per questo progetto, viene visualizzato un messaggio che ti informa che devi configurare la schermata per il consenso OAuth prima di poter utilizzare IAP.

    Fai clic su Configura la schermata per il consenso.

  3. Nella scheda Schermata consenso OAuth della pagina Credenziali, compila i seguenti campi:

    • Se il tuo account fa parte di un'organizzazione Google Workspace, seleziona Esterno e fai clic su Crea. Per iniziare, l'app sarà disponibile solo per gli utenti consentiti esplicitamente.

    • Nel campo Nome applicazione, inserisci IAP Example.

    • Nel campo Email dell'assistenza, inserisci il tuo indirizzo email.

    • Nel campo Dominio autorizzato, inserisci la parte del nome host dell'URL dell'app, ad esempio iap-example-999999.uc.r.appspot.com. Premi il tasto Enter dopo aver inserito il nome host nel campo.

    • Nel campo Link alla home page dell'applicazione, inserisci l'URL dell'app, ad esempio https://iap-example-999999.uc.r.appspot.com/.

    • Nel campo Riga delle norme sulla privacy dell'applicazione, utilizza lo stesso URL del link della home page per scopi di test.

  4. Fai clic su Salva. Quando ti viene chiesto di creare le credenziali, puoi chiudere la finestra.

  5. Nella console Google Cloud, vai alla pagina Identity-Aware Proxy.

    Vai alla pagina di Identity-Aware Proxy

  6. Per aggiornare la pagina, fai clic su Aggiorna . La pagina mostra un elenco di risorse che puoi proteggere.

  7. Nella colonna IAP, fai clic per attivare IAP per l'app.

  8. Nel browser, visita di nuovo web-site-url.

  9. Al posto della pagina web, è disponibile una schermata di accesso per autenticarti. Quando esegui l'accesso, ti viene negato l'accesso perché IAP non dispone di un elenco di utenti ai quali consentire l'accesso all'app.

Aggiungi utenti autorizzati all'app

  1. Nella console Google Cloud, vai alla pagina Identity-Aware Proxy.

    Vai alla pagina di Identity-Aware Proxy

  2. Seleziona la casella di controllo per l'app App Engine, quindi fai clic su Aggiungi entità.

  3. Inserisci allAuthenticatedUsers, quindi seleziona il ruolo Utente applicazione web con protezione IAP/Cloud IAP.

  4. Fai clic su Salva.

Ora qualsiasi utente autenticato da Google può accedere all'app. Se vuoi, puoi limitare ulteriormente l'accesso aggiungendo solo una o più persone o gruppi come entità:

  • Qualsiasi indirizzo email Gmail o Google Workspace

  • L'indirizzo email di un gruppo Google

  • Un nome di dominio Google Workspace

Accedere all'app

  1. Nel browser, vai a web-site-url.

  2. Per aggiornare la pagina, fai clic su Aggiorna .

  3. Nella schermata di accesso, accedi con le tue credenziali Google.

    Nella pagina viene visualizzata una pagina "Ciao user-email-address" con il tuo indirizzo email.

    Se visualizzi ancora la stessa pagina di prima, potrebbe essersi verificato un problema con il browser che non aggiorna completamente le nuove richieste ora che hai abilitato IAP. Chiudi tutte le finestre del browser, riaprile e riprova.

Concetti di autenticazione

Un'app può autenticare i propri utenti e limitare l'accesso solo agli utenti autorizzati in vari modi. I metodi di autenticazione comuni, con un livello di impegno decrescente per l'app, sono elencati nelle sezioni seguenti.

Opzione Vantaggi Svantaggi
Autenticazione app
  • L'app può essere eseguita su qualsiasi piattaforma, con o senza connessione a internet
  • Gli utenti non devono utilizzare altri servizi per gestire l'autenticazione
  • L'app deve gestire le credenziali utente in modo sicuro, evitando di divulgarle
  • L'app deve conservare i dati delle sessioni per gli utenti che hanno eseguito l'accesso
  • L'app deve fornire registrazione utente, modifiche alle password e recupero della password
OAuth2
  • L'app può essere eseguita su qualsiasi piattaforma connessa a internet, inclusa una workstation per sviluppatori
  • L'app non richiede la registrazione degli utenti, le modifiche alla password o le funzioni di recupero della password.
  • Il rischio della divulgazione di informazioni degli utenti è delegato a un altro servizio
  • Nuove misure di sicurezza per l'accesso gestite al di fuori dell'app
  • Gli utenti devono registrarsi al servizio di identità
  • L'app deve conservare i dati delle sessioni per gli utenti che hanno eseguito l'accesso
IAP
  • L'app non richiede alcun codice per gestire gli utenti, l'autenticazione o lo stato della sessione
  • L'app non ha credenziali utente che potrebbero essere state violate
  • L'app può essere eseguita solo su piattaforme supportate dal servizio. In particolare, alcuni servizi Google Cloud che supportano IAP, come App Engine.

Autenticazione gestita dall'app

Con questo metodo, l'app gestisce in autonomia ogni aspetto dell'autenticazione utente. L'app deve mantenere il proprio database di credenziali utente e gestire le sessioni utente, oltre a fornire funzionalità per la gestione di account utente e password, per il controllo delle credenziali degli utenti, nonché per la gestione dei problemi, del controllo e dell'aggiornamento delle sessioni utente con ogni accesso autenticato. Il seguente diagramma illustra il metodo di autenticazione gestita dall'app.

Flusso gestito dall'applicazione

Come mostrato nel diagramma, dopo che l'utente esegue l'accesso, l'app crea e conserva le informazioni sulla sua sessione. Quando l'utente invia una richiesta all'app, la richiesta deve includere informazioni sulla sessione di cui l'app è responsabile di verifica.

Il vantaggio principale di questo approccio è che è indipendente e sotto il controllo dell'app. L'app non deve essere nemmeno disponibile su internet. Lo svantaggio principale è che ora l'app è responsabile di fornire tutte le funzionalità di gestione dell'account e di proteggere tutti i dati sensibili delle credenziali.

Autenticazione esterna con OAuth2

Una buona alternativa alla gestione di tutto all'interno dell'app è utilizzare un servizio di identità esterno, come Google, che gestisce tutte le funzionalità e le informazioni degli account utente ed è responsabile della salvaguardia delle credenziali sensibili. Quando un utente tenta di accedere all'app, la richiesta viene reindirizzata al servizio di identità, che autentica l'utente e poi reindirizza la richiesta all'app fornendo le informazioni di autenticazione necessarie. Per maggiori informazioni, consulta Utilizzo di OAuth 2.0 per applicazioni server web.

Il seguente diagramma illustra l'autenticazione esterna con il metodo OAuth2.

Flusso OAuth2

Il flusso nel diagramma inizia quando l'utente invia una richiesta di accesso all'app. Anziché rispondere direttamente, l'app reindirizza il browser dell'utente alla piattaforma di identità di Google, che mostra una pagina per accedere a Google. Dopo l'accesso, il browser dell'utente viene reindirizzato all'app. Questa richiesta include informazioni che l'app può utilizzare per cercare informazioni sull'utente ora autenticato, e l'app ora risponde all'utente.

Questo metodo presenta molti vantaggi per l'app. Delega al servizio esterno tutti i rischi e le funzionalità di gestione degli account, in modo da migliorare la sicurezza dell'accesso e dell'account senza che l'app debba essere modificata. Tuttavia, come mostrato nel diagramma precedente, l'app deve avere accesso a internet per utilizzare questo metodo. L'app è anche responsabile della gestione delle sessioni dopo l'autenticazione dell'utente.

Identity-Aware Proxy

Il terzo approccio, trattato in questo tutorial, prevede l'utilizzo di IAP per gestire l'autenticazione e la gestione delle sessioni con qualsiasi modifica all'app. IAP intercetta tutte le richieste web all'app, blocca tutte le richieste non autenticate e passa altri dati di identità utente aggiunti a ogni richiesta.

La gestione delle richieste è mostrata nel seguente diagramma.

Flusso IAP

Le richieste degli utenti vengono intercettate da IAP, che blocca le richieste non autenticate. Le richieste autenticate vengono trasmesse all'app, a condizione che l'utente autenticato sia nell'elenco degli utenti autorizzati. Alle richieste trasmesse tramite IAP sono aggiunte intestazioni che identificano l'utente che ha effettuato la richiesta.

L'app non deve più gestire alcuna informazione sull'account utente o sulla sessione. Qualsiasi operazione che richieda l'identificazione di un identificatore univoco per l'utente può ottenerlo direttamente da ogni richiesta web in entrata. Tuttavia, questo può essere utilizzato solo per i servizi di computing che supportano IAP, come App Engine e i bilanciatori del carico. Non puoi utilizzare IAP su una macchina di sviluppo locale.

Esegui la pulizia

Per evitare che al tuo Account Google Cloud vengano addebitati costi relativi alle risorse utilizzate in questo tutorial, elimina il progetto che contiene le risorse oppure mantieni il progetto ed elimina le singole risorse.

  1. Nella console Google Cloud, vai alla pagina Gestisci risorse.

    Vai a Gestisci risorse

  2. Nell'elenco dei progetti, seleziona il progetto che vuoi eliminare, quindi fai clic su Elimina.
  3. Nella finestra di dialogo, digita l'ID del progetto e fai clic su Chiudi per eliminare il progetto.