Autenticazione tra servizi

Oltre ad autenticare gli utenti, potresti dover consentire ad altri servizi di interagire con la tua API. Mentre le applicazioni client possono fornire agli utenti un prompt di accesso web per inviare le proprie credenziali, è necessario un altro approccio per la comunicazione sicura tra servizi. Questa pagina mostra l'approccio che consigliamo di implementare per l'autenticazione tra i servizi e fornisce un codice campione.

Panoramica

Per identificare un servizio che invia richieste alla tua API, utilizza un service account. Il servizio chiamante utilizza la chiave privata del 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 tra servizi nella tua API e nel servizio chiamante:

  1. Crea un account di servizio e una chiave da utilizzare per il servizio chiamante.
  2. Aggiungi il supporto per l'autenticazione nel documento OpenAPI per il tuo servizio Cloud Endpoints.
  3. Aggiungi codice al servizio chiamante che:

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

Prima di inoltrare la richiesta alla tua API, ESP convalida che le attestazioni nel JWT corrispondano alla configurazione nel documento OpenAPI. ESP non verifica le autorizzazioni Cloud Identity che hai concesso al account di servizio.

Prerequisiti

In questa pagina si presuppone che tu abbia già:

Creare un account di servizio con una chiave

Ti serve un account di servizio con un file di chiave privata che il servizio chiamante utilizza per firmare il JWT. Se più servizi inviano richieste alla tua API, puoi creare un account di servizio per rappresentare tutti i servizi chiamanti. 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 chiamante.

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

Per creare un account di servizio e una chiave:

Google Cloud Console

  1. Crea un account di servizio:

    1. Nella Google Cloud console vai a Crea service account pagina.

      Vai alla pagina Crea service account

    2. Seleziona il progetto che vuoi utilizzare.

    3. Nel campo Nome service account, inserisci un nome.

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

    5. Fai clic su Crea.

    6. Fai clic su Fine.

      Non chiudere la finestra del browser. Lo utilizzerai nel prossimo passaggio.

  2. Crea una chiave del account di servizio:

    1. Nella Google Cloud console, fai clic sull'indirizzo email del 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 JSON contenente la chiave privata del account di servizio viene scaricato sul computer.
    5. Fai clic su Chiudi.

gcloud

Puoi eseguire i seguenti comandi utilizzando Google Cloud CLI sulla tua macchina locale o in Cloud Shell.

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

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

    gcloud projects list
    
  3. Imposta il progetto predefinito. Sostituisci PROJECT_ID con l' Google Cloud ID progetto 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 del account di servizio che hai appena creato.

    gcloud iam service-accounts list
    
  6. Aggiungi il ruolo Creatore token account di servizio. Sostituisci SA_EMAIL_ADDRESS con l'indirizzo email del 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 del account di servizio nella directory di lavoro corrente. Sostituisci FILE_NAME con il nome che vuoi utilizzare 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

Per ulteriori informazioni sui comandi precedenti, consulta la documentazione di riferimento gcloud.

Per informazioni sulla protezione della chiave privata, vedi Best practice per la gestione delle credenziali.

Configurare l'API per supportare l'autenticazione

Per abilitare l'autenticazione del account di servizio per i servizi che chiamano il tuo gateway, modifica gli oggetti di sicurezza nel documento OpenAPI in modo che ESP convalidi le attestazioni nel JWT firmato. Le modifiche variano in base alla versione delle specifiche OpenAPI utilizzata.

OpenAPI 2.0

  1. Aggiungi il account di servizio come emittente nelle specifiche OpenAPI:
    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. Potresti sostituirla con il nome del account di servizio o con un nome che identifica il servizio chiamante.
    • Sostituisci SA_EMAIL_ADDRESS con l'indirizzo email del service account's.
    • Puoi definire più definizioni di sicurezza nelle specifiche OpenAPI, ma ogni definizione deve avere un x-google-issuer diverso. Se hai creato service account separati per ogni servizio chiamante, 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. (Facoltativo) Aggiungi x-google-audiences alla sezione securityDefinitions. Se non aggiungi x-google-audiences, ESP richiede che l'"aud" (pubblico) attestazione nel JWT sia nel formato https://SERVICE_NAME, dove SERVICE_NAME è il nome del tuo servizio ESP, che hai configurato nel campo host del documento OpenAPI.
  3. Aggiungi una sezione security a livello principale del file (senza rientro o nidificazione) da applicare all'intera API o a livello di metodo da applicare a un metodo specifico. Se utilizzi le sezioni security sia a livello di API sia a livello di metodo, le impostazioni a livello di metodo sostituiscono quelle a livello di API.
    security:
      - DEFINITION_NAME: []
    • Sostituisci DEFINITION_NAME con il nome che hai utilizzato nella securityDefinitions sezione.
    • Se hai più di una definizione nella sezione securityDefinitions, aggiungile nella sezione security, ad esempio:
      security:
        - service-1: []
        - service-2: []
  4. Esegui il deployment delle specifiche OpenAPI aggiornate. Prima che ESP inoltri una richiesta alla tua API, ESP verifica:
    • La firma del JWT utilizzando la chiave pubblica, che si trova nell'URI specificato nel x-google-jwks_uricampo nelle specifiche OpenAPI.
    • Che l'attestazione "iss"(emittente) nel JWT corrisponda al valore specificato nel x-google-issuercampo.
    • Che l'attestazione "aud"(pubblico) nel JWT contenga il nome del tuo servizio ESP o corrisponda a uno dei valori specificati nel campo x-google-audiences.
    • Che il token non sia scaduto utilizzando l'attestazione "exp"(ora di scadenza).

OpenAPI 3.x

  1. Aggiungi il account di servizio come emittente nelle specifiche OpenAPI:
    components:
      securitySchemes:
        SCHEME_NAME:
          type: oauth2
          flows:
           implicit:
             authorizationUrl: ""
             scopes: {}
          x-google-auth:
            issuer: SA_EMAIL_ADDRESS
            jwksUri: https://www.googleapis.com/robot/v1/metadata/x509/SA_EMAIL_ADDRESS
            audiences:
              - 848149964201.apps.googleusercontent.com
              - 841077041629.apps.googleusercontent.com
            jwtLocations:
              - header: Authorization
                valuePrefix: "Bearer "
    security:
      - SCHEME_NAME: []
    • Sostituisci SCHEME_NAME con una stringa che identifica questo schema di sicurezza. Potresti sostituirla con il nome del account di servizio o con un nome che identifica il servizio chiamante.
    • Sostituisci SA_EMAIL_ADDRESS con l'indirizzo email del service account's.
    • Puoi definire più schemi di sicurezza nelle specifiche OpenAPI, ma ogni definizione deve avere un issuer diverso. Se hai creato service account separati per ogni servizio chiamante, puoi creare una definizione di sicurezza per ogni account di servizio, ad esempio:
      components:
        securitySchemes:
          service-1:
            type: oauth2
            flows:
             implicit:
               authorizationUrl: ""
               scopes: {}
            x-google-auth:
              issuer: "service-1@example-project-12345.iam.gserviceaccount.com"
              jwksUri: https://www.googleapis.com/robot/v1/metadata/x509/service-1@example-project-12345.iam.gserviceaccount.com
              jwtLocations:
                - header: Authorization
                  valuePrefix: "Bearer "
          service-2:
            type: oauth2
            flows:
             implicit:
               authorizationUrl: ""
               scopes: {}
            x-google-auth:
              issuer: "service-2@example-project-12345.iam.gserviceaccount.com"
              jwksUri: "https://www.googleapis.com/robot/v1/metadata/x509/service-2@example-project-12345.iam.gserviceaccount.com"
              jwtLocations:
                - header: Authorization
                  valuePrefix: "Bearer "
  2. (Facoltativo) Aggiungi audiences alla sezione securitySchemes. Se non aggiungi audiences, ESP richiede che l'"aud" (pubblico) attestazione nel JWT sia nel formato https://SERVICE_NAME, dove SERVICE_NAME è il nome del tuo servizio ESP, che hai configurato nel campo host del documento OpenAPI.
  3. Aggiungi una sezione security a livello principale del file (senza rientro o nidificazione) da applicare all'intera API o a livello di metodo da applicare a un metodo specifico. Se utilizzi le sezioni security sia a livello di API sia a livello di metodo, le impostazioni a livello di metodo sostituiscono quelle a livello di API.
    security:
      - SCHEME_NAME: []
    • Sostituisci SCHEME_NAME con il nome che hai utilizzato nella sezione securitySchemes.
    • Se hai più di una definizione nella sezione securitySchemes, aggiungile nella sezione security, ad esempio:
      security:
        - service-1: []
        - service-2: []
  4. Esegui il deployment delle specifiche OpenAPI aggiornate. Prima che ESP inoltri una richiesta alla tua API, ESP verifica:
    • La firma del JWT utilizzando la chiave pubblica, che si trova nell'URI specificato nel jwksUricampo nelle specifiche OpenAPI.
    • Che l'attestazione "iss"(emittente) nel JWT corrisponda al valore specificato nel issuercampo.
    • Che l'attestazione "aud"(pubblico) nel JWT contenga il nome del tuo servizio ESP o corrisponda a uno dei valori specificati nel audiencescampo.
    • Che il token non sia scaduto utilizzando l'attestazione "exp"(ora di scadenza).

Effettuare una richiesta autenticata a un'API Endpoints

Per effettuare una richiesta autenticata, il servizio chiamante invia un JWT firmato dal account di servizio specificato nel documento OpenAPI. Il servizio chiamante deve:

  1. Creare un JWT e firmarlo con la chiave privata del account di servizio.
  2. Inviare il JWT firmato in una richiesta all'API.

Il seguente codice campione illustra questa procedura per le lingue selezionate. Per effettuare una richiesta autenticata in altre lingue, consulta jwt.io per un elenco delle librerie supportate.

  1. Nel servizio chiamante, aggiungi la seguente funzione e trasmetti i seguenti parametri:
    Java
    • saKeyfile: il percorso completo del file della chiave privata del account di servizio.
    • saEmail: l'indirizzo email del account di servizio.
    • audience: se hai aggiunto il campo x-google-audiences al documento OpenAPI, imposta audience su uno dei valori che hai specificato per x-google-audiences. In caso contrario, imposta audience su https://SERVICE_NAME, dove SERVICE_NAME è il nome del tuo servizio Endpoints.
    • expiryLength: l'ora di scadenza del JWT, in secondi.
    Python
    • sa_keyfile: il percorso completo del file della chiave privata del account di servizio.
    • sa_email: l'indirizzo email del account di servizio.
    • audience: se hai aggiunto il campo x-google-audiences al documento OpenAPI, imposta audience su uno dei valori che hai specificato per x-google-audiences. In caso contrario, imposta audience su https://SERVICE_NAME, dove SERVICE_NAME è il nome del tuo servizio Endpoints.
    • expiry_length: l'ora di scadenza del JWT, in secondi.
    Go
    • saKeyfile: il percorso completo del file della chiave privata del account di servizio.
    • saEmail: l'indirizzo email del account di servizio.
    • audience: se hai aggiunto il campo x-google-audiences al documento OpenAPI, imposta audience su uno dei valori che hai specificato per x-google-audiences. In caso contrario, imposta audience su https://SERVICE_NAME, dove SERVICE_NAME è il nome del tuo servizio Endpoints.
    • expiryLength: l'ora di scadenza del JWT, in secondi.

    La funzione crea un JWT, lo firma utilizzando il file della 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
    
    
    Go
    
    // 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 := os.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 seguente funzione per inviare il JWT firmato nell'intestazione Authorization: Bearer nella 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()
    
    
    Go
    
    // 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 := io.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}" \
  "${ENDPOINTS_HOST}/echo"

dove ENDPOINTS_HOST e TOKEN sono variabili di ambiente che contengono rispettivamente il nome host dell'API e il token di autenticazione.

Ricevere risultati autenticati nella tua API

In genere, ESP inoltra tutte le intestazioni che riceve. Tuttavia, sostituisce l'intestazione Authorization originale quando l'indirizzo del backend è specificato da x-google-backend nelle specifiche OpenAPI o da BackendRule nella configurazione del servizio gRPC.

ESP invierà il risultato dell'autenticazione nell'intestazione X-Endpoint-API-UserInfo all'API di backend. Ti consigliamo di utilizzare questa intestazione anziché l'intestazione Authorization originale. Questa intestazione è una stringa che codifica base64url un oggetto JSON. Il formato dell'oggetto JSON è diverso tra ESPv2 ed ESP. Per ESPv2, l'oggetto JSON è esattamente il payload JWT originale. Per ESP, l'oggetto JSON utilizza nomi di campi diversi e inserisce il payload JWT originale nel campo claims. Per ulteriori informazioni sul formato, vedi Gestire i JWT nel servizio di backend.

Passaggi successivi