驗證使用者

驗證功能讓可擴充服務 Proxy (ESP) 識別呼叫您服務方法的使用者,然後據此決定是否讓他們使用該方法 (授權)。本頁面說明如何搭配使用驗證與 gRPC 服務專用的 Cloud Endpoints,包括如何在 gRPC 服務中設定 ESP,以支援通過驗證的要求,以及如何從 gRPC 用戶端呼叫通過驗證的方法。

ESP 支援多種驗證方法,包括 Firebase、Auth0 和 Google ID 權杖,您可以在 gRPC API 設定中設定這些方法。在各種情況下,用戶端都需要在其要求中提供識別用的 JSON Web Token (JWT)。ESP 會代表 API 驗證權杖,因此您不需要自行新增任何特殊驗證程式碼。

雖然驗證和 API 金鑰規定都能限制可呼叫服務方法的使用者,但兩者提供的安全性等級不同,傳送給所呼叫服務的資訊也不一樣。如要進一步瞭解 API 金鑰和驗證之間的差異,以及適合使用各項機制的情境,請參閱「API 金鑰的使用時機與原因」。

如需使用驗證的完整範例,請參閱「使用服務帳戶進行驗證」,其中會將驗證新增至教學課程中的 Bookstore 服務。

為 ESP 設定驗證功能

您可以在 Endpoints for gRPC 服務的 gRPC 服務設定 YAML 檔案中,使用 authentication 區段設定驗證。您會將驗證方法和驗證來源的詳細資料指定為 providers,其中:

  • id 值用於 rules 中時,可識別驗證供應商:這通常會使用驗證方法的名稱,但並非必要。

  • issuer 值是必要權杖的核發者,因此會指定驗證方法。

  • jwks_uri 值是提供者公開金鑰的 URI,用於驗證權杖。部分驗證方法不需要指定這項資訊,例如 Google ID 權杖,ESP 會自動取得這項資訊。

  • jwt_locations 用於定義要擷取 JWT 的位置。

您可以在同一個檔案中定義多個安全供應商,但每個供應商必須要有不同的 issuer。詳情請參閱 AuthProvider。

如要指定要使用這些驗證規定的 API 方法,請使用 rules,詳情請參閱AuthenticationRule。

下列範例說明如何在 gRPC 服務中設定 ESP,以使用部分支援的驗證方法:

firebase

如何支援 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

auth0

如何支援 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

Google ID 憑證

如要支援使用 Google ID 權杖進行驗證,請按照以下步驟操作:

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

自訂

如何支援自訂驗證:

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

如要進行 Firebase 驗證,必須填寫 audiences 欄位,且內容必須是 Firebase 專案 ID。如果是其他驗證方式,則為選填。如果未指定,ESP 會接受 aud 憑證附加資訊中含有後端服務名稱 (格式為 https://SERVICE_NAME) 的所有 JWT。如要允許其他用戶端 ID 存取後端服務,您可以在 audiences 欄位中指定允許的用戶端 ID,並以半形逗號分隔值。ESP 接著會接受 aud 憑證附加資訊中含有允許用戶端 ID 的 JWT。

從 gRPC 呼叫通過驗證的方法

如果方法需要驗證,gRPC 用戶端就須使用方法呼叫,以中繼資料的形式傳遞驗證權杖,其中金鑰為 authorization,值為 Bearer <JWT_TOKEN>。請參閱下列範例,瞭解如何使用 Python、Node.js 或 Java 呼叫 Bookstore 範例:

Python

def run(
    host, port, api_key, auth_token, timeout, use_tls, servername_override, ca_path

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/grpc-js');
  const protoLoader = require('@grpc/proto-loader');
  const path = require('path');

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

  const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true,
  });

  const protoObj = grpc.loadPackageDefinition(packageDefinition).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);
    }
  });
};

用戶端如何取得要傳送的有效 JWT,取決於驗證方法。

在您的 API 中接收驗證結果

ESP 通常會轉發所有接收到的標頭。不過,如果後端位址是由 OpenAPI 規格中的 x-google-backend 或 gRPC 服務設定中的 BackendRule 指定,系統就會覆寫原始的 Authorization 標頭。

ESP 會將 X-Endpoint-API-UserInfo 中的驗證結果傳送至後端 API。建議使用這個標頭,取代原始的 Authorization 標頭。這個標頭是字串,會 base64url 編碼 JSON 物件。ESPv2 和 ESP 的 JSON 物件格式不同。 如果是 ESPv2,JSON 物件就是原始 JWT 酬載。對於 ESP,JSON 物件會使用不同的欄位名稱,並將原始 JWT 酬載放在 claims 欄位下。如要進一步瞭解格式,請參閱「在後端服務中處理 JWT」。

後續步驟