Autenticazione degli utenti

L'autenticazione consente a Extensible Service Proxy (ESP) di identificare gli utenti che chiamano i metodi del tuo servizio e, in base a questi dati, di decidere se consentire loro di utilizzare quel metodo (autorizzazione). In questa pagina viene spiegato come funziona l'autenticazione con Cloud Endpoints per i servizi gRPC, incluso come configurare ESP in un servizio gRPC per supportare le richieste autenticate e come chiamare metodi autenticati da un client gRPC.

ESP supporta più metodi di autenticazione, tra cui Firebase, Auth0 e token ID Google, che possono essere impostati come parte della configurazione dell'API gRPC. In ogni caso, il client deve fornire un token web JSON (JWT) di identificazione nelle richieste. L'ESP convalida il token per conto dell'API, perciò non è necessario aggiungere un codice di autenticazione particolare.

Sebbene i requisiti di autenticazione e di chiave API consentano di limitare gli utenti che possono chiamare i metodi del tuo servizio, non forniscono lo stesso livello di sicurezza e forniscono informazioni diverse al servizio chiamato. Per saperne di più sulle differenze tra chiavi API e autenticazione, nonché quando è opportuno utilizzare ogni schema, consulta Quando e perché utilizzare le chiavi API.

Per un esempio completo di utilizzo dell'autenticazione, consulta la pagina relativa all'autenticazione con un account di servizio, che aggiunge l'autenticazione al servizio Bookstore dai nostri Tutorial.

Configurare l'autenticazione per ESP

Puoi configurare l'autenticazione per un servizio Endpoints per gRPC nel suo file YAML per la configurazione del servizio gRPC, utilizzando la sezione authentication. Devi specificare il metodo di autenticazione e i dettagli dell'origine di autenticazione come providers, dove:

  • Il valore id viene utilizzato per identificare il provider di autenticazione quando viene utilizzato in rules: in genere viene utilizzato il nome del metodo di autenticazione, ma non è necessario.

  • Il valore issuer è l'emittente dei token richiesti, quindi specifica il metodo di autenticazione.

  • Il valore jwks_uri è l'URI della chiave pubblica del provider, utilizzata per convalidare i token. Alcuni metodi di autenticazione non richiedono di specificarlo, ad esempio i token ID Google, dove ESP ottiene le informazioni automaticamente.

  • jwt_locations viene utilizzato per definire le località per l'estrazione del JWT.

Puoi definire più provider di sicurezza nello stesso file, ma ognuno deve avere un valore issuer diverso Per ulteriori informazioni, visita la pagina AuthProvider.

Devi specificare i metodi API che vuoi utilizzare per questi requisiti di autenticazione utilizzando rules, come descritto in AuthenticationRule.

Gli esempi riportati di seguito mostrano come configurare ESP in un servizio gRPC per alcuni metodi di autenticazione supportati:

firebase

Per supportare l'autenticazione Firebase:

authentication:
  providers:
  - id: firebase
    jwks_uri: https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com
    # Replace FIREBASE-PROJECT-ID with your Firebase project ID
    issuer: https://securetoken.google.com/FIREBASE-PROJECT-ID
    audiences: "FIREBASE-PROJECT-ID"
    # Optional.
    jwt_locations:
    # expect header "jwt-header-foo": "jwt-prefix-foo<TOKEN>"
    - header: "jwt-header-foo"
      value_prefix: "jwt-prefix-foo"
    - query: "jwt_query_bar"
  rules:
  - selector: "*"
    requirements:
      - provider_id: firebase

autenticazione0

Per supportare l'autenticazione Auth0:

authentication:
  providers:
  - id: auth0_jwk
    # Replace YOUR-ACCOUNT-NAME with your service account's email address.
    issuer: https://YOUR-ACCOUNT-NAME.auth0.com/
    jwks_uri: "https://YOUR-ACCOUNT-NAME.auth0.com/.well-known/jwks.json"
    # Optional. Replace YOUR-CLIENT-ID with your client ID
    audiences: "YOUR-CLIENT-ID"
  rules:
  - selector: "*"
    requirements:
      - provider_id: auth0_jwk

Token ID Google

Per supportare l'autenticazione utilizzando un token ID Google:

authentication:
  providers:
  - id: google_id_token
    # This "issuer" field has to match the field "iss" in the JWT token.
     # Sometime it is "accounts.google.com".
    issuer: https://accounts.google.com
    # Optional. Replace YOUR-CLIENT-ID with your client ID
    audiences: "YOUR-CLIENT-ID"
  rules:
  - selector: "*"
    requirements:
      - provider_id: google_id_token

custom

Per supportare l'autenticazione personalizzata:

authentication:
  providers:
  - id: custom_auth_id
    # The value below should be unique
    issuer: issuer of the token
    jwks_uri: url to the public key
    # Optional. Replace YOUR-CLIENT-ID with your client ID
    audiences: "YOUR-CLIENT-ID"
 rules:
 - selector: "*"
   requirements:
     - provider_id: custom_auth_id

Per l'autenticazione Firebase, il campo audiences è obbligatorio e deve essere l'ID progetto Firebase. Per tutti gli altri metodi di autenticazione, è facoltativa. Se non specificato, ESP accetta tutti i JWT con il nome del servizio di backend sotto forma di https://SERVICE_NAME nella rivendicazione aud. Per consentire l'accesso agli ID client aggiuntivi al servizio di backend, puoi specificare gli ID client consentiti nel campo audiences utilizzando valori separati da virgola. ESP accetta quindi i JWT con gli ID client nella lista consentita nella rivendicazione aud.

Chiamata di un metodo autenticato da gRPC

Se un metodo richiede l'autenticazione, i client gRPC devono passare il token di autenticazione come metadati con la chiamata al metodo, dove la chiave è authorization e il valore è Bearer <JWT_TOKEN>. Ecco un esempio di come fare quando chiami l'esempio di Bookstore in Python, Node.js o Java:

Python

def run(host, port, api_key, auth_token, timeout, use_tls, servername_override, ca_path):
    """Makes a basic ListShelves call against a gRPC Bookstore server."""

    if use_tls:
        with open(ca_path, 'rb') as f:
            creds = grpc.ssl_channel_credentials(f.read())
        channel_opts = ()
        if servername_override:
            channel_opts += ((
                        'grpc.ssl_target_name_override', servername_override,),)
        channel = grpc.secure_channel(f'{host}:{port}', creds, channel_opts)
    else:
        channel = grpc.insecure_channel(f'{host}:{port}')

    stub = bookstore_pb2_grpc.BookstoreStub(channel)
    metadata = []
    if api_key:
        metadata.append(('x-api-key', api_key))
    if auth_token:
        metadata.append(('authorization', 'Bearer ' + auth_token))
    shelves = stub.ListShelves(empty_pb2.Empty(), timeout, metadata=metadata)
    print(f'ListShelves: {shelves}')

Java

private static final class Interceptor implements ClientInterceptor {
  private final String apiKey;
  private final String authToken;

  private static Logger LOGGER = Logger.getLogger("InfoLogging");

  private static Metadata.Key<String> API_KEY_HEADER =
      Metadata.Key.of("x-api-key", Metadata.ASCII_STRING_MARSHALLER);
  private static Metadata.Key<String> AUTHORIZATION_HEADER =
      Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);

  public Interceptor(String apiKey, String authToken) {
    this.apiKey = apiKey;
    this.authToken = authToken;
  }

  @Override
  public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
      MethodDescriptor<ReqT,RespT> method, CallOptions callOptions, Channel next) {
    LOGGER.info("Intercepted " + method.getFullMethodName());
    ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);

    call = new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(call) {
      @Override
      public void start(Listener<RespT> responseListener, Metadata headers) {
        if (apiKey != null && !apiKey.isEmpty()) {
          LOGGER.info("Attaching API Key: " + apiKey);
          headers.put(API_KEY_HEADER, apiKey);
        }
        if (authToken != null && !authToken.isEmpty()) {
          System.out.println("Attaching auth token");
          headers.put(AUTHORIZATION_HEADER, "Bearer " + authToken);
        }
        super.start(responseListener, headers);
      }
    };
    return call;
  }
}

Node.js

const makeGrpcRequest = (JWT_AUTH_TOKEN, API_KEY, HOST, GREETEE) => {
  // Uncomment these lines to set their values
  // const JWT_AUTH_TOKEN = 'YOUR_JWT_AUTH_TOKEN';
  // const API_KEY = 'YOUR_API_KEY';
  // const HOST = 'localhost:50051'; // The IP address of your endpoints host
  // const GREETEE = 'world';

  // Import required libraries
  const grpc = require('grpc');
  const path = require('path');

  // Load protobuf spec for an example API
  const PROTO_PATH = path.join(__dirname, '/protos/helloworld.proto');
  const protoObj = grpc.load(PROTO_PATH).helloworld;

  // Create a client for the protobuf spec
  const client = new protoObj.Greeter(HOST, grpc.credentials.createInsecure());

  // Build gRPC request
  const metadata = new grpc.Metadata();
  if (API_KEY) {
    metadata.add('x-api-key', API_KEY);
  } else if (JWT_AUTH_TOKEN) {
    metadata.add('authorization', `Bearer ${JWT_AUTH_TOKEN}`);
  }

  // Execute gRPC request
  client.sayHello({name: GREETEE}, metadata, (err, response) => {
    if (err) {
      console.error(err);
    }

    if (response) {
      console.log(response.message);
    }
  });
};

Il modo in cui il client riceve un JWT valido da inviare dipende dal metodo di autenticazione.

Ricezione dei risultati dell'autenticazione nell'API

ESP in genere inoltra tutte le intestazioni ricevute. Tuttavia, sostituisce 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 di autenticazione nel campo X-Endpoint-API-UserInfo all'API di backend. Ti consigliamo di utilizzare questa intestazione invece dell'intestazione originale Authorization. Questa intestazione è una stringa che base64url codifica per un oggetto JSON. Il formato degli oggetti JSON è diverso da ESPv2 a ESP. Per ESPv2, l'oggetto JSON è esattamente il payload JWT originale. Per ESP, l'oggetto JSON utilizza nomi di campi diversi e posiziona il payload JWT originale nel campo claims. Per ulteriori informazioni sul formato, consulta Gestire i JWT nel servizio di backend.

Passaggi successivi