Autenticazione tra servizi

Oltre ad autenticare le richieste degli utenti finali, potresti voler autenticare i servizi (utenti non umani) che effettuano richieste alla tua API. In questa pagina viene spiegato come utilizzare gli account di servizio per fornire l'autenticazione a persone fisiche o servizi.

Panoramica

Per identificare un servizio che invia richieste alla tua API, utilizza un account di servizio. Il servizio chiamante utilizza la chiave privata dell'account di servizio per firmare un token web JSON (JWT) sicuro e invia il JWT firmato nella richiesta alla tua API.

Per implementare l'autenticazione dell'account di servizio nell'API e nel servizio di chiamata:

  1. Crea un account di servizio e una chiave per il servizio di chiamata da utilizzare.
  2. Aggiungi il supporto per l'autenticazione nella configurazione API del tuo servizio API Gateway.
  3. Aggiungi al servizio di chiamata un codice che:

    • Crea un JWT e lo firma con la chiave privata dell'account di servizio.
    • Invia il JWT firmato in una richiesta all'API.

API Gateway verifica che le attestazioni nel JWT corrispondano alla configurazione nella configurazione API prima di inoltrare la richiesta all'API. API Gateway non controlla le autorizzazioni di Cloud Identity che hai concesso nell'account di servizio.

Prerequisiti

Questa pagina presuppone che tu abbia già:

Creazione di un account di servizio con una chiave

Devi avere un account di servizio con un file di chiave privata che il servizio di chiamata utilizza per firmare il JWT. Se hai più di un servizio che invia richieste alla tua API, puoi creare un account di servizio per rappresentare tutti i servizi di chiamata. Se devi distinguere tra i servizi, ad esempio perché potrebbero avere autorizzazioni diverse, puoi creare un account di servizio e una chiave per ogni servizio di chiamata.

Questa sezione mostra come utilizzare la console Google Cloud e lo strumento a riga di comando gcloud per creare l'account di servizio e il file della chiave privata e per assegnare all'account di servizio il ruolo Creatore token account di servizio. Per informazioni sull'utilizzo di un'API per eseguire questa attività, consulta Creazione e gestione degli account di servizio.

Per creare un account di servizio con una chiave:

Console Google Cloud

Crea un account di servizio:

  1. Nella console Google Cloud, vai a Crea account di servizio.

    Vai a Crea account di servizio

  2. Seleziona un progetto.

  3. Inserisci un nome nel campo Nome account di servizio. La console Google Cloud compila il campo ID account di servizio in base a questo nome.

  4. (Facoltativo) Nel campo Descrizione account di servizio, inserisci una descrizione.

  5. Fai clic su Crea.

  6. Fai clic sul campo Seleziona un ruolo.

    In Tutti i ruoli, seleziona Account di servizio > Creatore token account di servizio.

  7. Fai clic su Continua.

  8. Fai clic su Fine per completare la creazione dell'account di servizio.

    Non chiudere la finestra del browser. Lo utilizzerai nella prossima procedura.

Crea una chiave dell'account di servizio:

  1. Nella console Google Cloud, fai clic sull'indirizzo email dell'account di servizio che hai creato.
  2. Fai clic su Chiavi.
  3. Fai clic su Aggiungi chiave, quindi su Crea nuova chiave.
  4. Fai clic su Crea. Un file di chiave JSON viene scaricato sul computer.
  5. Fai clic su Chiudi.

gcloud

Puoi eseguire i comandi seguenti utilizzando Google Cloud CLI sulla tua macchina locale o all'interno di Cloud Shell.

  1. Imposta l'account predefinito per gcloud. Se disponi di più account, assicurati di scegliere quello che si trova nel progetto Google Cloud che vuoi utilizzare.

    gcloud auth login
    
  2. Visualizza gli ID dei tuoi progetti Google Cloud.

    gcloud projects list
    
  3. Imposta il progetto predefinito. Sostituisci PROJECT_ID con l'ID del progetto Google Cloud che vuoi utilizzare.

    gcloud config set project PROJECT_ID
  4. Crea un account di servizio. Sostituisci SA_NAME e SA_DISPLAY_NAME con il nome e il nome visualizzato che vuoi utilizzare.

    gcloud iam service-accounts create SA_NAME \
      --display-name "SA_DISPLAY_NAME"
    
  5. Visualizza l'indirizzo email dell'account di servizio appena creato.

    gcloud iam service-accounts list
    
  6. Aggiungi il ruolo Creatore token account di servizio. Sostituisci SA_EMAIL_ADDRESS con l'indirizzo email dell'account di servizio.

    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member serviceAccount:SA_EMAIL_ADDRESS \
      --role roles/iam.serviceAccountTokenCreator
    
  7. Crea un file della chiave dell'account di servizio nella directory di lavoro corrente. Sostituisci FILE_NAME con il nome che vuoi usare per il file della chiave. Per impostazione predefinita, il comando gcloud crea un file JSON.

    gcloud iam service-accounts keys create FILE_NAME.json \
      --iam-account SA_EMAIL_ADDRESS
    

Consulta il riferimento di gcloud per ulteriori informazioni sui comandi precedenti.

Per informazioni sulla salvaguardia della chiave privata, consulta le best practice per la gestione delle credenziali.

Configurazione dell'API per supportare l'autenticazione

Quando crei una configurazione API per il gateway, devi specificare un account di servizio che il gateway utilizza per interagire con altri servizi. Per abilitare l'autenticazione degli account di servizio per i servizi che chiamano il gateway, modifica l'oggetto requisito di sicurezza e l'oggetto definizioni di sicurezza nella configurazione API. Seguendo i passaggi riportati di seguito, API Gateway potrà convalidare le attestazioni nel JWT firmato utilizzato dalle chiamate ai servizi.

  1. Aggiungi l'account di servizio come emittente nella configurazione API.

    securityDefinitions:
      DEFINITION_NAME:
        authorizationUrl: ""
        flow: "implicit"
        type: "oauth2"
        x-google-issuer: "SA_EMAIL_ADDRESS"
        x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/SA_EMAIL_ADDRESS"
    
    • Sostituisci DEFINITION_NAME con una stringa che identifica questa definizione di sicurezza. Puoi sostituirlo con il nome dell'account di servizio o con un nome che identifichi il servizio di chiamata.
    • Sostituisci SA_EMAIL_ADDRESS con l'indirizzo email dell'account di servizio.
    • Puoi definire più definizioni di sicurezza nella configurazione API, ma ogni definizione deve avere un valore x-google-issuer diverso. Se hai creato account di servizio separati per ogni servizio di chiamata, puoi creare una definizione di sicurezza per ogni account di servizio, ad esempio:
    securityDefinitions:
      service-1:
        authorizationUrl: ""
        flow: "implicit"
        type: "oauth2"
        x-google-issuer: "service-1@example-project-12345.iam.gserviceaccount.com"
        x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/service-1@example-project-12345.iam.gserviceaccount.com"
      service-2:
        authorizationUrl: ""
        flow: "implicit"
        type: "oauth2"
        x-google-issuer: "service-2@example-project-12345.iam.gserviceaccount.com"
        x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/service-2@example-project-12345.iam.gserviceaccount.com"
    
  2. Se vuoi, aggiungi x-google-audiences alla sezione securityDefinitions. Se non aggiungi x-google-audiences, API Gateway richiede che l'attestazione "aud" (pubblico) nel JWT sia nel formato https://SERVICE_NAME, dove SERVICE_NAME è il nome del servizio API Gateway, che hai configurato nel campo host del documento OpenAPI.

  3. Aggiungi una sezione security al livello superiore del file (non in posizione rientrata o nidificata) da applicare all'intera API oppure a livello di metodo per applicare a un metodo specifico. Se utilizzi le sezioni security sia a livello di API che a livello di metodo, le impostazioni a livello di metodo prevalgono su quelle a livello di API.

    security:
      - DEFINITION_NAME: []
    
    • Sostituisci DEFINITION_NAME con il nome che hai utilizzato nella sezione securityDefinitions.
    • Se hai più di una definizione nella sezione securityDefinitions, aggiungile nella sezione security, ad esempio:

      security:
        - service-1: []
        - service-2: []
      
  4. Esegui il deployment della configurazione API aggiornata.

Prima che API Gateway inoltri una richiesta alla tua API, API Gateway verifica:

  • La firma del JWT mediante la chiave pubblica, che si trova nell'URI specificato nel campo x-google-jwks_uri della configurazione API.
  • Che la dichiarazione "iss" (emittente) nel JWT corrisponda al valore specificato nel campo x-google-issuer.
  • Che l'attestazione "aud" (pubblico) nel JWT contenga il nome del servizio del gateway API o corrisponda a uno dei valori specificati nel campo x-google-audiences.
  • Che il token non sia scaduto utilizzando la dichiarazione "exp" (scadenza).

Per ulteriori informazioni su x-google-issuer, x-google-jwks_uri e x-google-audiences, consulta la pagina relativa alle estensioni OpenAPI.

Invio di una richiesta autenticata a un'API API Gateway

Per effettuare una richiesta autenticata, il servizio di chiamata invia un JWT firmato dall'account di servizio specificato nella configurazione API. Il servizio di chiamata deve:

  1. Crea un JWT e firmalo con la chiave privata dell'account di servizio.
  2. Invia il JWT firmato in una richiesta all'API.

Il seguente codice campione illustra questa procedura per alcuni linguaggi. Per effettuare una richiesta autenticata in altri linguaggi, fai riferimento a jwt.io per un elenco delle librerie supportate.

  1. Nel servizio chiamante, aggiungi la funzione seguente e passa i seguenti parametri:
    Java
    • saKeyfile: il percorso completo del file delle chiavi private dell'account di servizio.
    • saEmail: l'indirizzo email dell'account di servizio.
    • audience: se hai aggiunto il campo x-google-audiences alla configurazione API, imposta audience su uno dei valori specificati per x-google-audiences. In caso contrario, imposta audience su https://SERVICE_NAME, dove SERVICE_NAME è il nome del tuo servizio API Gateway.
    • expiryLength: la scadenza del JWT, in secondi.
    Python
    • sa_keyfile: il percorso completo del file delle chiavi private dell'account di servizio.
    • sa_email: l'indirizzo email dell'account di servizio.
    • audience: se hai aggiunto il campo x-google-audiences alla configurazione API, imposta audience su uno dei valori specificati per x-google-audiences. In caso contrario, imposta audience su https://SERVICE_NAME, dove SERVICE_NAME è il nome del tuo servizio API Gateway.
    • expiry_length: la scadenza del JWT, in secondi.
    Vai
    • saKeyfile: il percorso completo del file delle chiavi private dell'account di servizio.
    • saEmail: l'indirizzo email dell'account di servizio.
    • audience: se hai aggiunto il campo x-google-audiences alla configurazione API, imposta audience su uno dei valori specificati per x-google-audiences. In caso contrario, imposta audience su https://SERVICE_NAME, dove SERVICE_NAME è il nome del tuo servizio API Gateway.
    • expiryLength: la scadenza del JWT, in secondi.

    La funzione crea un JWT, lo firma utilizzando il file di chiave privata e restituisce il JWT firmato.

    Java
    /**
     * Generates a signed JSON Web Token using a Google API Service Account
     * utilizes com.auth0.jwt.
     */
    public static String generateJwt(final String saKeyfile, final String saEmail,
        final String audience, final int expiryLength)
        throws FileNotFoundException, IOException {
    
      Date now = new Date();
      Date expTime = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiryLength));
    
      // Build the JWT payload
      JWTCreator.Builder token = JWT.create()
          .withIssuedAt(now)
          // Expires after 'expiryLength' seconds
          .withExpiresAt(expTime)
          // Must match 'issuer' in the security configuration in your
          // swagger spec (e.g. service account email)
          .withIssuer(saEmail)
          // Must be either your Endpoints service name, or match the value
          // specified as the 'x-google-audience' in the OpenAPI document
          .withAudience(audience)
          // Subject and email should match the service account's email
          .withSubject(saEmail)
          .withClaim("email", saEmail);
    
      // Sign the JWT with a service account
      FileInputStream stream = new FileInputStream(saKeyfile);
      ServiceAccountCredentials cred = ServiceAccountCredentials.fromStream(stream);
      RSAPrivateKey key = (RSAPrivateKey) cred.getPrivateKey();
      Algorithm algorithm = Algorithm.RSA256(null, key);
      return token.sign(algorithm);
    }
    Python
    def generate_jwt(
        sa_keyfile,
        sa_email="account@project-id.iam.gserviceaccount.com",
        audience="your-service-name",
        expiry_length=3600,
    ):
        """Generates a signed JSON Web Token using a Google API Service Account."""
    
        now = int(time.time())
    
        # build payload
        payload = {
            "iat": now,
            # expires after 'expiry_length' seconds.
            "exp": now + expiry_length,
            # iss must match 'issuer' in the security configuration in your
            # swagger spec (e.g. service account email). It can be any string.
            "iss": sa_email,
            # aud must be either your Endpoints service name, or match the value
            # specified as the 'x-google-audience' in the OpenAPI document.
            "aud": audience,
            # sub and email should match the service account's email address
            "sub": sa_email,
            "email": sa_email,
        }
    
        # sign with keyfile
        signer = google.auth.crypt.RSASigner.from_service_account_file(sa_keyfile)
        jwt = google.auth.jwt.encode(signer, payload)
    
        return jwt
    
    
    Vai
    
    // generateJWT creates a signed JSON Web Token using a Google API Service Account.
    func generateJWT(saKeyfile, saEmail, audience string, expiryLength int64) (string, error) {
    	now := time.Now().Unix()
    
    	// Build the JWT payload.
    	jwt := &jws.ClaimSet{
    		Iat: now,
    		// expires after 'expiryLength' seconds.
    		Exp: now + expiryLength,
    		// Iss must match 'issuer' in the security configuration in your
    		// swagger spec (e.g. service account email). It can be any string.
    		Iss: saEmail,
    		// Aud must be either your Endpoints service name, or match the value
    		// specified as the 'x-google-audience' in the OpenAPI document.
    		Aud: audience,
    		// Sub and Email should match the service account's email address.
    		Sub:           saEmail,
    		PrivateClaims: map[string]interface{}{"email": saEmail},
    	}
    	jwsHeader := &jws.Header{
    		Algorithm: "RS256",
    		Typ:       "JWT",
    	}
    
    	// Extract the RSA private key from the service account keyfile.
    	sa, err := ioutil.ReadFile(saKeyfile)
    	if err != nil {
    		return "", fmt.Errorf("Could not read service account file: %w", err)
    	}
    	conf, err := google.JWTConfigFromJSON(sa)
    	if err != nil {
    		return "", fmt.Errorf("Could not parse service account JSON: %w", err)
    	}
    	block, _ := pem.Decode(conf.PrivateKey)
    	parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
    	if err != nil {
    		return "", fmt.Errorf("private key parse error: %w", err)
    	}
    	rsaKey, ok := parsedKey.(*rsa.PrivateKey)
    	// Sign the JWT with the service account's private key.
    	if !ok {
    		return "", errors.New("private key failed rsa.PrivateKey type assertion")
    	}
    	return jws.Encode(jwsHeader, jwt, rsaKey)
    }
    
  2. Nel servizio chiamante, aggiungi la funzione seguente per inviare il JWT firmato nell'intestazione Authorization: Bearer della richiesta all'API:
    Java
    /**
     * Makes an authorized request to the endpoint.
     */
    public static String makeJwtRequest(final String signedJwt, final URL url)
        throws IOException, ProtocolException {
    
      HttpURLConnection con = (HttpURLConnection) url.openConnection();
      con.setRequestMethod("GET");
      con.setRequestProperty("Content-Type", "application/json");
      con.setRequestProperty("Authorization", "Bearer " + signedJwt);
    
      InputStreamReader reader = new InputStreamReader(con.getInputStream());
      BufferedReader buffReader = new BufferedReader(reader);
    
      String line;
      StringBuilder result = new StringBuilder();
      while ((line = buffReader.readLine()) != null) {
        result.append(line);
      }
      buffReader.close();
      return result.toString();
    }
    Python
    def make_jwt_request(signed_jwt, url="https://your-endpoint.com"):
        """Makes an authorized request to the endpoint"""
        headers = {
            "Authorization": "Bearer {}".format(signed_jwt.decode("utf-8")),
            "content-type": "application/json",
        }
        response = requests.get(url, headers=headers)
        print(response.status_code, response.content)
        response.raise_for_status()
    
    
    Vai
    
    // makeJWTRequest sends an authorized request to your deployed endpoint.
    func makeJWTRequest(signedJWT, url string) (string, error) {
    	client := &http.Client{
    		Timeout: 10 * time.Second,
    	}
    
    	req, err := http.NewRequest("GET", url, nil)
    	if err != nil {
    		return "", fmt.Errorf("failed to create HTTP request: %w", err)
    	}
    	req.Header.Add("Authorization", "Bearer "+signedJWT)
    	req.Header.Add("content-type", "application/json")
    
    	response, err := client.Do(req)
    	if err != nil {
    		return "", fmt.Errorf("HTTP request failed: %w", err)
    	}
    	defer response.Body.Close()
    	responseData, err := ioutil.ReadAll(response.Body)
    	if err != nil {
    		return "", fmt.Errorf("failed to parse HTTP response: %w", err)
    	}
    	return string(responseData), nil
    }
    

Quando invii una richiesta utilizzando un JWT, per motivi di sicurezza, ti consigliamo di inserire il token di autenticazione nell'intestazione Authorization: Bearer. Ad esempio:

curl --request POST \
  --header "Authorization: Bearer ${TOKEN}" \
  "${GATEWAY_URL}/echo"

dove GATEWAY_URL e TOKEN sono variabili di ambiente contenenti rispettivamente l'URL del gateway di cui hai eseguito il deployment e il token di autenticazione.

Ricezione di risultati autenticati nell'API

API Gateway in genere inoltra tutte le intestazioni che riceve. Tuttavia, sostituisce l'intestazione Authorization originale quando l'indirizzo di backend è specificato da x-google-backend nella configurazione API.

API Gateway invierà il risultato dell'autenticazione in X-Apigateway-Api-Userinfo all'API di backend. Ti consigliamo di utilizzare questa intestazione anziché l'intestazione Authorization originale. Questa intestazione è codificata in base64url e contiene il payload JWT.

Passaggi successivi