使用 API 密钥限制 API 访问权限

您可以使用 API 密钥来限制对 API 中的特定 API 方法或所有方法的访问。本页面介绍如何限制为只允许拥有 API 密钥的客户端访问 API,同时还演示了如何创建 API 密钥。

Extensible Service Proxy (ESP) 使用 Service Control API 来验证 API 密钥及其与项目中已启用的 API 之间的关联。如果您在 API 中设置了 API 密钥要求,则对受保护的方法、类或 API 的请求将被拒绝,除非这些请求具有在您的项目或其他项目(属于已获授权可启用您的 API 的开发者)中生成的密钥。系统不会记录创建 API 密钥时所用的项目,也不会将其添加到请求标头中。不过,您可以按照针对特定使用方项目进行过滤中所述,在 Endpoints > 服务页面上查看与客户端关联的 Google Cloud 项目。

如需了解应在哪个 Google Cloud 项目中创建 API 密钥,请参阅共享受 API 密钥保护的 API

默认情况下,在 gRPC 服务中,访问所有 API 方法都需要 API 密钥。您可以停用整个 API 或特定方法的 API 密钥要求。要执行此操作,请按照以下步骤,在服务配置中添加 usage 部分,并配置规则和选择器

限制或授予对所有 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 的访问权限),请执行以下操作:

  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 客户端调用 API。

gRPC 客户端

如果某方法需要 API 密钥,则 gRPC 客户端需要将密钥值作为 x-api-key 元数据与其方法调用一起传递。

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('{}:{}'.format(host, port), creds, channel_opts)
    else:
        channel = grpc.insecure_channel('{}:{}'.format(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('ListShelves: {}'.format(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;
  }
}

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

HTTP 客户端

如果您使用 Cloud Endpoints 来实现 gRPC 的 HTTP 转码功能,则 HTTP 客户端可以将密钥作为查询参数发送,具体方式与其在处理 OpenAPI 服务时的方式一样。

共享受 API 密钥保护的 API

API 密钥与其创建时所在的 Google Cloud 项目相关联。如果您已决定访问您的 API 需要提供 API 密钥,请回答以下几个问题,以便确定从中创建 API 密钥的 GCP 项目:

  • 您是否需要区分 API 的调用者以便使用 Endpoints 的配额等功能?
  • API 的所有调用者是否都有自己的 Google Cloud 项目?
  • 您是否需要设置不同的 API 密钥限制

在确定从中创建 API 密钥的 Google Cloud 项目时,您可以下面的决策树为指导。

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 密钥限制的每位客户创建一个 API 密钥。
  2. 将此 API 密钥提供给对应的调用者。

为所有调用者创建一个 API 密钥

如果您不需要区分 API 的调用者,也不需要添加 API 限制,但仍想施加 API 密钥要求(例如,用于阻止匿名访问),则可以创建一个 API 密钥供所有调用者使用。

要为所有调用者创建一个 API 密钥,请执行以下操作:
  1. 在配置了您的 API 的项目中或在启用了您的 API 的项目中,为所有调用者创建一个 API 密钥。
  2. 将该 API 密钥提供给每个调用者。

后续步骤