驗證使用者

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

ESP 支援多種驗證方法,包括 FirebaseAuth0Google 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」。

後續步驟