Nutzer authentifizieren

Mithilfe der Authentifizierungsfunktion kann der Extensible Service Proxy (ESP) die Nutzer identifizieren, die Methoden Ihres Dienstes aufrufen. Er entscheidet daraufhin, ob er ihnen die Verwendung der jeweiligen Methode erlaubt, d. h. sie autorisiert. Auf dieser Seite wird beschrieben, wie die Authentifizierung mit Cloud Endpoints für gRPC-Dienste funktioniert, darunter die Konfiguration des ESP in einem gRPC-Dienst zur Unterstützung authentifizierter Anfragen und das Aufrufen authentifizierter Methoden von einem gRPC-Client.

Der ESP unterstützt mehrere Authentifizierungsmethoden wie Firebase, Auth0 und Google-ID-Tokens. Sie können alle diese Methoden im Rahmen Ihrer gRPC API-Konfiguration einrichten. Der Client muss zur Identifizierung in jedem Fall ein JSON Web Token (JWT) in den Anfragen bereitstellen. Der ESP prüft das Token für Ihre API, sodass Sie keinen speziellen Authentifizierungscode hinzufügen müssen.

Sie können Aufrufe der Methoden Ihres Dienstes sowohl mithilfe einer Authentifizierung als auch durch API-Schlüssel einschränken, wobei die Verfahren unterschiedlich sicher sind. Außerdem erhält der aufgerufene Dienst mit jeder Methode andere Informationen. Unter API-Schlüssel effizient nutzen erfahren Sie mehr über die Unterschiede zwischen API-Schlüsseln und der Authentifizierung sowie der effizienten Verwendung jedes Schemas.

Ein Praxisbeispiel für die Verwendung der Authentifizierung finden Sie unter Authentifizierung mit einem Dienstkonto. Dabei wird die Authentifizierung dem Bookstore-Dienst aus unseren Anleitungen hinzugefügt.

Authentifizierung für ESP konfigurieren

Sie konfigurieren die Authentifizierung für einen gRPC-Endpoints-Dienst in der zugehörigen YAML-Datei der gRPC API-Konfiguration im Abschnitt authentication. Geben Sie die Authentifizierungsmethode sowie die Details der Authentifizierungsquelle als providers an. Dabei gilt Folgendes:

  • Der Wert id dient bei Verwendung in rules zum Identifizieren des Authentifizierungsanbieters. In der Regel wird dabei der Name der Authentifizierungsmethode verwendet, was jedoch nicht immer der Fall ist.

  • Der Wert issuer ist der Aussteller der erforderlichen Tokens und legt somit die Authentifizierungsmethode fest.

  • Der Wert jwks_uri ist der URI für den öffentlichen Schlüssel des Anbieters. Er dient zum Prüfen der Tokens. Für manche Authentifizierungsmethoden wie etwa Google-ID-Tokens muss diese Information nicht angegeben werden, da sie automatisch vom ESP abgerufen wird.

  • Mit jwt_locations werden die Speicherorte zum Extrahieren des JWT definiert.

Sie können in derselben Datei mehrere Sicherheitsanbieter definieren, wobei jeder als issuer einen anderen Aussteller haben muss. Weitere Informationen finden Sie unter AuthProvider.

Die für diese Authentifizierungsanforderungen zu verwendenden API-Methoden legen Sie mithilfe von rules fest. Eine Beschreibung hierzu finden Sie unter AuthenticationRule.

Die folgenden Beispiele zeigen, wie Sie den ESP für verschiedene unterstützte Authentifizierungsmethoden in einem gRPC-Dienst einrichten:

Firebase

So aktivieren Sie die Firebase-Authentifizierung:

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

Auth0

So aktivieren Sie die Auth0-Authentifizierung:

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

Google-ID-Token

So unterstützen Sie die Authentifizierung mithilfe eines Google-ID-Tokens:

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

Benutzerdefiniert

So aktivieren Sie benutzerdefinierte Authentifizierung:

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

Für die Firebase-Authentifizierung ist das Feld audiences erforderlich und muss Ihre Firebase-Projekt-ID sein. Für alle anderen Authentifizierungsmethoden ist es optional. Wenn nicht angegeben, akzeptiert der ESP alle JWTs mit dem Back-End-Dienstnamen im Format https://SERVICE_NAME im Anspruch aud. Sie können die zulässigen Client-IDs im Feld audiences mithilfe kommagetrennter Werte angeben, um den Zugriff auf den Back-End-Dienst für weitere Client-IDs zuzulassen. ESP akzeptiert dann die JWTs mit den auf der weißen Liste befindlichen Client-IDs im aud-Anspruch.

Authentifizierungsmethode von gRPC aufrufen

Wenn eine Methode eine Authentifizierung erfordert, müssen gRPC-Clients mit ihrem Methodenaufruf das Authentifizierungstoken als Metadaten übergeben. Der Schlüssel hierfür ist authorization und der Wert ist Bearer <JWT_TOKEN>. In den Beispielen sehen Sie, wie dies beim Aufrufen des Bookstore-Beispiels in Python, Node.js oder Java durchgeführt wird:

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);
    }
  });
};

Wie der Client ein gültiges zu sendendes JWT abruft, hängt von der Authentifizierungsmethode ab.

Authentifizierungsergebnisse in Ihrer API empfangen

Der ESP leitet in der Regel alle empfangenen Header weiter. Der ESP überschreibt aber möglicherweise den ursprünglichen Authorization-Header, wenn die Back-End-Adresse in der OpenAPI-Spezifikation durch x-google-backend oder in der gRPC-Dienstkonfiguration mit BackendRule angegeben wird.

Der ESP sendet das Authentifizierungsergebnis in X-Endpoint-API-UserInfo an die Back-End-API. Wir empfehlen, diesen Header anstelle des ursprünglichen Authorization-Headers zu verwenden. Dieser Header ist ein String, mit dem base64url ein JSON-Objekt codiert. Das JSON-Objektformat unterscheidet sich zwischen ESPv2 und ESP. Bei ESPv2 ist das JSON-Objekt genau die ursprüngliche JWT-Nutzlast. Für den ESP verwendet das JSON-Objekt unterschiedliche Feldnamen und legt die ursprüngliche JWT-Nutzlast unter dem Feld claims ab. Weitere Informationen zum Format finden Sie unter JWTs im Back-End-Dienst verarbeiten.

Nächste Schritte