Autenticazione tra servizi

Oltre all'autenticazione degli utenti, potresti aver bisogno di consentire ad altri servizi di interagire con la tua API. Mentre il cliente applicazioni possono fornire agli utenti una richiesta di accesso al Web per inviare per la sicurezza delle credenziali, è necessario un altro approccio la comunicazione. Questa pagina mostra l'approccio che consigliamo di implementare l'autenticazione tra servizi e fornisce il codice campione.

Panoramica

Per identificare un servizio che invia richieste alla tua API, utilizza una account di servizio. Il servizio di chiamata utilizza la chiave privata dell'account di servizio per firmare una JWT (JSON Web Token) e invia il JWT firmato nella richiesta all'API.

Per implementare l'autenticazione da servizio a servizio nell'API e nel servizio di chiamata:

  1. Creare 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 al servizio chiamante il 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.

ESP convalida che le attestazioni nel JWT corrispondano ai configurazione nel documento OpenAPI prima di inoltrare la richiesta alla tua API. ESP non verifica la presenza di Cloud Identity che hai concesso per l'account di servizio.

Prerequisiti

In questa pagina si 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 utilizzato dal servizio chiamante per firmare il JWT. Se più di un servizio invia richieste alla tua API, puoi creare un solo account di servizio per rappresentare tutti i servizi di chiamata. Se devono distinguere tra i vari servizi, ad esempio, autorizzazioni diverse: puoi creare un account di servizio e una chiave per ogni chiamata completamente gestito di Google Cloud.

Questa sezione mostra come utilizzare la console Google Cloud e gcloud a strumento a riga di comando per creare l'account di servizio e il file della chiave privata assegna all'account di servizio Creatore token account di servizio ruolo. 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 e una chiave:

Console Google Cloud

  1. Crea un account di servizio:

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

      Vai alla pagina Crea account di servizio

    2. Seleziona il progetto che vuoi utilizzare.

    3. Nel campo Nome account di servizio, inserisci un nome.

    4. (Facoltativo) Nel campo Descrizione account di servizio, inserisci una o l'audiodescrizione.

    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 Fine.

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

  2. Crea una chiave dell'account di servizio:

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

gcloud

Puoi eseguire i comandi seguenti utilizzando Google Cloud CLI sul tuo server o all'interno di Cloud Shell.

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

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

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

    gcloud iam service-accounts list
    
  6. Aggiungi il ruolo Creatore token account di servizio. Sostituisci SA_EMAIL_ADDRESS con il nome 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 attuale. Sostituisci FILE_NAME con il nome che vuoi assegnare per il file di 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 le Riferimento gcloud per ulteriori informazioni sui comandi precedenti.

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

Configurazione dell'API per supportare l'autenticazione

Devi avere un oggetto requisito di sicurezza e un oggetto definizioni di sicurezza nel documento OpenAPI affinché ESP possa convalidare le attestazioni firmato JWT.

  1. Aggiungi l'account di servizio come emittente nel documento 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. Ti consigliamo di sostituirlo nome dell'account di servizio o un nome che identifica il servizio chiamante.
    • Sostituisci SA_EMAIL_ADDRESS con il servizio all'indirizzo email dell'account.
    • Puoi definire più definizioni di sicurezza nel documento OpenAPI, ma ogni definizione deve avere un valore x-google-issuer diverso. Se disponi creare account di servizio separati per ogni servizio di chiamata, crea 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, ESP richiede che L'affermazione "aud" (pubblico) nel JWT è nel formato https://SERVICE_NAME, dove SERVICE_NAME è il nome degli endpoint configurato nel campo host della pagina OpenAPI a meno che il flag Viene utilizzato --disable_jwt_audience_service_name_check. Se il flag viene utilizzato x-google-audiences non è specificato, il campo JWT aud non è selezionato.

  3. Se vuoi, aggiungi x-google-jwt-locations alla sezione securityDefinitions. Puoi utilizzare questo valore per definire una località JWT personalizzata. Le località JWT predefinite sono l'intestazione Authorization (preceduta da "Bearer"), l'intestazione X-Goog-Iap-Jwt-Assertion o il parametro di query access_token. Nota:

    • Se specifichi l'istruzione x-google-jwt-locations, Endpoints ignora tutte le località predefinite.
    • x-google-jwt-locations è supportato solo da ESPv2.
  4. Aggiungi una sezione security al livello superiore del file (non rientrato o nidificato) per applicarlo all'intera API o a livello di metodo da applicare a un metodo specifico. Se utilizzi sezioni security sia nell'API e, a livello di metodo, le impostazioni a livello di metodo Impostazioni a livello di API.

    security:
      - DEFINITION_NAME: []
    
    • Sostituisci DEFINITION_NAME con il nome che utilizzata nella sezione securityDefinitions.
    • Se è presente più di una definizione nella sezione securityDefinitions, aggiungilo nella sezione security, ad esempio:

      security:
        - service-1: []
        - service-2: []
      
  5. Esegui il deployment del documento OpenAPI aggiornato. Sostituisci OPENAPI_DOC con il nome della tua OpenAPI documento.

    gcloud endpoints services deploy OPENAPI_DOC

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

  • La firma del JWT mediante la chiave pubblica, che si trova nell'URI specificato nel campo x-google-jwks_uri del documento OpenAPI.
  • Che la dichiarazione "iss" (emittente) nel JWT corrisponda al valore specificato nel x-google-issuer.
  • Che la dichiarazione "aud" (pubblico) nel JWT contenga i tuoi Il nome del servizio Endpoints o che corrisponde a uno dei valori che hai specificato nel campo x-google-audiences.
  • Che il token non sia scaduto utilizzando la rivendicazione "exp" (tempo di scadenza).

Per ulteriori informazioni su x-google-issuer, x-google-jwks_uri, x-google-audiences e x-google-jwt-locations, vedi Estensioni OpenAPI.

Esecuzione di una richiesta autenticata a un'API Endpoints

Per effettuare una richiesta autenticata, il servizio chiamante invia un JWT firmato dell'account di servizio specificato nel documento OpenAPI. Il servizio di chiamata devono:

  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 codice campione seguente mostra questo processo per alcuni linguaggi. Per effettuare una richiesta autenticata in altre lingue, fai riferimento jwt.io per un elenco delle librerie supportate.

  1. Nel servizio chiamante, aggiungi la funzione seguente e passala come segue parametri:
    Java
    • saKeyfile: il percorso completo della chiave privata dell'account di servizio .
    • saEmail: l'indirizzo email dell'account di servizio.
    • audience: se hai aggiunto il campo x-google-audiences al documento OpenAPI, imposta audience su uno dei valori che che hai specificato per x-google-audiences. In caso contrario, imposta Da audience a https://SERVICE_NAME, dove SERVICE_NAME è il tuo endpoint il nome del servizio.
    • expiryLength: la scadenza del JWT, espressa in secondi.
    di Gemini Advanced.
    .
    .
    Python
    • sa_keyfile: il percorso completo della chiave privata dell'account di servizio .
    • sa_email: l'indirizzo email dell'account di servizio.
    • audience: se hai aggiunto il campo x-google-audiences al documento OpenAPI, imposta audience su uno dei valori che che hai specificato per x-google-audiences. In caso contrario, imposta Da audience a https://SERVICE_NAME, dove SERVICE_NAME è il tuo endpoint il nome del servizio.
    • expiry_length: la scadenza del JWT, espressa in secondi.
    di Gemini Advanced.
    .
    .
    Vai
    • saKeyfile: il percorso completo della chiave privata dell'account di servizio .
    • saEmail: l'indirizzo email dell'account di servizio.
    • audience: se hai aggiunto il campo x-google-audiences al documento OpenAPI, imposta audience su uno dei valori che che hai specificato per x-google-audiences. In caso contrario, imposta Da audience a https://SERVICE_NAME, dove SERVICE_NAME è il tuo endpoint il nome del servizio.
    • expiryLength: la scadenza del JWT, espressa 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);
    }
    di Gemini Advanced.
    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
    
    
    di Gemini Advanced.
    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 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();
    }
    di Gemini Advanced.
    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()
    
    
    di Gemini Advanced.
    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
    }
    

Per motivi di sicurezza, quando invii una richiesta utilizzando un JWT, ti consigliamo di inserire il token di autenticazione nella sezione Authorization: Bearer intestazione. Ad esempio:

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

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

Ricezione di risultati autenticati nell'API

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

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

Passaggi successivi