透過 API 金鑰限制 API 存取權

您可以使用 API 金鑰限制存取特定 API 方法或 API 中所有方法。本頁面說明如何限制具 API 金鑰之用戶端的 API 存取權,並示範 API 金鑰的建立方式。

可擴充服務 Proxy (ESP) 使用 Service ControlAPI 驗證 API 金鑰,以及該金鑰與專案所啟用 API 之間的關聯。如果您在 API 中設定必須要有 API 金鑰,除非有在您的專案或其他開發人員的專案中產生的金鑰,且您已對這些開發人員授予啟用您 API 的存取權,否則只要是對受保護的方法、類別或 API 提出要求,一律會遭到拒絕。系統不會記錄建立該 API 金鑰的專案,也不會在要求標頭新增該專案。不過,您可以按照「篩選特定消費者專案」一節所述,依序點選「Endpoints」 >「Service」,查看與某個用戶端相關聯的 Google Cloud專案。

如要進一步瞭解應該在哪個 Google Cloud 專案中建立 API 金鑰,請參閱「共用受 API 金鑰保護的 API」一節。

根據預設,在 gRPC 服務中,所有 API 方法均規定必須持 API 金鑰進行存取。您可以停用整個 API 或特定方法的 API 金鑰規定。如要停用 API 金鑰規定,請按照下列程序中的說明,在服務設定中新增一個 用途區段,然後設定規則和選取器

限制或授予所有 API 方法的存取權

若要指定不需 API 金鑰即可存取 API:

  1. 在文字編輯器中開啟專案的 gRPC 服務設定檔案,然後尋找或新增 usage 區段。

  2. usage 區段中指定 allow_unregistered_calls 規則,如下所示。selector 中的萬用字元 "*" 表示該項規則適用於 API 中的所有方法。

    usage:
      rules:
      # All methods can be called without an API Key.
      - selector: "*"
        allow_unregistered_calls: true
    

移除方法的 API 金鑰限制

若要關閉特定方法 API 金鑰驗證 (即使您限制了該 API 的 API 存取權):

  1. 在文字編輯器中開啟專案的 gRPC 服務設定檔案,然後尋找或新增 usage 區段:

  2. usage 區段中指定 allow_unregistered_calls 規則,如下所示。selector 表示該項規則只適用於指定的方法,在這個範例中就是指 ListShelves

    usage:
      rules:
      # ListShelves method can be called without an API Key.
      - selector: endpoints.examples.bookstore.Bookstore.ListShelves
        allow_unregistered_calls: true
    

使用 API 金鑰呼叫 API

呼叫 API 的方式視您在 gRPC 用戶端或 HTTP 用戶端進行呼叫而異。

gRPC 用戶端

若方法規定必須驗證 API 金鑰,gRPC 用戶端就需要使用方法呼叫,以 x-api-key 中繼資料的形式傳遞金鑰值。

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

Go

func main() {
	flag.Parse()

	// Set up a connection to the server.
	conn, err := grpc.Dial(*addr, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewGreeterClient(conn)

	if *keyfile != "" {
		log.Printf("Authenticating using Google service account key in %s", *keyfile)
		keyBytes, err := ioutil.ReadFile(*keyfile)
		if err != nil {
			log.Fatalf("Unable to read service account key file %s: %v", *keyfile, err)
		}

		tokenSource, err := google.JWTAccessTokenSourceFromJSON(keyBytes, *audience)
		if err != nil {
			log.Fatalf("Error building JWT access token source: %v", err)
		}
		jwt, err := tokenSource.Token()
		if err != nil {
			log.Fatalf("Unable to generate JWT token: %v", err)
		}
		*token = jwt.AccessToken
		// NOTE: the generated JWT token has a 1h TTL.
		// Make sure to refresh the token before it expires by calling TokenSource.Token() for each outgoing requests.
		// Calls to this particular implementation of TokenSource.Token() are cheap.
	}

	ctx := context.Background()
	if *key != "" {
		log.Printf("Using API key: %s", *key)
		ctx = metadata.AppendToOutgoingContext(ctx, "x-api-key", *key)
	}
	if *token != "" {
		log.Printf("Using authentication token: %s", *token)
		ctx = metadata.AppendToOutgoingContext(ctx, "Authorization", fmt.Sprintf("Bearer %s", *token))
	}

	// Contact the server and print out its response.
	name := defaultName
	if len(flag.Args()) > 0 {
		name = flag.Arg(0)
	}
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Greeting: %s", r.Message)
}

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

HTTP 用戶端

若您使用 Cloud Endpoints 進行 gRPC 的 HTTP 轉碼功能,HTTP 用戶端可以按照處理 OpenAPI 服務的方式,以查詢參數的形式傳送金鑰。

共用受 API 金鑰保護的 API

API 金鑰與建立金鑰時所屬的 Google Cloud 專案相關聯。如果您決定要有 API 金鑰才能存取您的 API,要在哪一個 Google Cloud 專案建立 API 金鑰則取決於以下問題:

  • 您是否需要區別不同的 API 呼叫端,以利使用配額之類的 Endpoints 功能?
  • API 的所有呼叫端是否都有自己的 Google Cloud 專案?
  • 您是否需要設定不同的 API 金鑰限制

您可以參考以下決策樹狀圖,決定應在哪一個 Google Cloud專案中建立 API 金鑰。

API 金鑰決策樹狀圖

授予 API 啟用權限

如果您需要區別 API 呼叫端,且每個呼叫端各自有其 Google Cloud 專案,您可以授予主要使用者權限,同意使用者在自己的 Google Cloud 專案中啟用您的 API。如此一來,您的 API 使用者就能自行建立 API 金鑰,以便與您的 API 一起使用。

例如,假設您的團隊已經建立可供公司內部不同用戶端程式使用的 API,且每個用戶端程式各自有其 Google Cloud專案。為區別 API 的呼叫端,必須在不同的 Google Cloud 專案中建立每個呼叫端的 API 金鑰。您可以將權限授予您的同事,讓對方能夠在與用戶端程式相關聯的 Google Cloud 專案中啟用您的 API。

如要讓使用者建立自己的 API 金鑰:

  1. 在設定 API 的 Google Cloud 專案中,將啟用 API 的權限授予每位使用者。
  2. 通知使用者,告知他們可以在自己的 Google Cloud專案啟用您的 API 建立 API 金鑰

為每個呼叫端建立單獨的 Google Cloud 專案

如果您需要區別 API 呼叫端,但並非所有呼叫端都有 Google Cloud 專案,您可以分別為每個呼叫端建立一個 Google Cloud 專案以及 API 金鑰。建立專案之前請先斟酌專案名稱,以利辨別與專案相關聯的呼叫端。

舉例來說,假設您的 API 有外部客戶,且您不知道呼叫 API 的用戶端程式是如何建立的。也許有些用戶端使用 Google Cloud 服務且有 Google Cloud 專案,有些則並非如此。您必須分別為每個呼叫端建立一個 Google Cloud 專案和 API 金鑰,以利區別不同的呼叫端。

如要為每個呼叫端分別建立一個 Google Cloud 專案和 API 金鑰:

  1. 為每個呼叫端建立單獨的專案。
  2. 在每個專案啟用您的 API建立 API 金鑰
  3. 將 API 金鑰分配給每個呼叫端。

為每個呼叫端建立一個 API 金鑰

若您不需要區別 API 呼叫端,但想要新增 API 金鑰限制,可以為同一個專案中的每個呼叫端分別建立一個 API 金鑰。

若要為同一個專案中的每個呼叫端建立一個 API 金鑰:

  1. 在已設定或已啟用 API 的專案中,為每個客戶建立 API 金鑰,且該金鑰應具有您需要的 API 金鑰限制
  2. 將 API 金鑰分配給每個呼叫端。

建立一個所有呼叫端通用的 API 金鑰

若您不需要區別 API 呼叫端,也不需要新增 API 限制,但仍想要規定使用 API 金鑰 (例如為防止匿名存取),可以建立一個可供所有呼叫端通用的 API 金鑰。

如要建立一個所有呼叫端通用的 API 金鑰:
  1. 在已設定或已啟用 API 的專案中,建立一個可供所有呼叫端通用的 API 金鑰。
  2. 將同一個 API 金鑰分配給每一個呼叫端。

後續步驟