除了对用户进行身份验证之外,您可能还需要允许其他服务与您的 API 进行交互。虽然客户端应用可以为用户提供网页登录提示,要求用户提交凭据进行身份验证,但您仍然需要另一种方法来确保安全的服务到服务通信。本页介绍了我们建议的在服务之间实现身份验证的方法,并提供了示例代码。
概览
要识别向您的 API 发送请求的服务,请使用服务账号。 调用服务会使用服务账号的私钥来签署安全的 JSON Web 令牌 (JWT),并将已签名的 JWT 随请求发送到您的 API。
要在您的 API 和调用服务中实现服务到服务身份验证,请执行以下操作:
- 为调用的服务创建服务账号和密钥。
- 在 Cloud Endpoints 服务的 OpenAPI 文档中添加对身份验证的支持。
将代码添加到调用服务:
- 创建 JWT 并使用服务账号的私钥为其签名。
- 将签名的 JWT 随请求发送到 API。
ESP 会在将请求转发给您的 API 之前验证 JWT 中的声明是否与 OpenAPI 文档中的配置相匹配。ESP 不会检查您已经在服务账号上授予的 Cloud Identity 权限。
前提条件
本页面假定您已经完成以下操作:
使用密钥创建服务账号
您需要一个包含调用服务用于签署 JWT 的私钥文件的服务账号。如果有多个服务向您的 API 发送请求,您可以创建一个服务账号来代表所有调用服务。如果您需要区分这些服务(例如,它们可能具有不同的权限),您可以为每个调用服务分别创建服务账号和密钥。
本部分介绍如何使用 Google Cloud 控制台和 gcloud
命令行工具来创建服务账号和私钥文件,以及为服务账号分配 Service Account Token Creator 角色。如需了解如何使用 API 执行此任务,请参阅创建和管理服务账号。
如需创建服务账号和密钥,请执行以下操作:
Google Cloud 控制台
创建服务账号:
在 Google Cloud 控制台中,进入创建服务账号页面。
选择要使用的项目。
在服务账号名称字段中,输入一个名称。
可选:在服务账号说明字段中,输入说明。
点击创建。
点击选择角色字段。在所有角色下,选择服务账号 > Service Account Token Creator。
点击完成。
不要关闭浏览器窗口。您将在下一步骤中用到它。
创建服务账号密钥:
- 在 Google Cloud 控制台中,点击您创建的服务账号的电子邮件地址。
- 点击密钥。
- 依次点击添加密钥和创建新密钥。
- 点击创建。包含服务账号私钥的 JSON 文件就会下载到您的计算机。
- 点击关闭。
gcloud
您可以使用本地机器上的 Google Cloud CLI 或在 Cloud Shell 中运行以下命令。
设置
gcloud
的默认账号。如果您有多个账号,请务必选择您要使用的 Google Cloud 项目中的账号。gcloud auth login
显示您的 Google Cloud 项目的 ID:
gcloud projects list
设置默认项目。将
PROJECT_ID
替换为您要使用的 Google Cloud 项目 ID。gcloud config set project PROJECT_ID
创建一个服务账号。将
SA_NAME
和SA_DISPLAY_NAME
替换为您要使用的名称和显示名。gcloud iam service-accounts create SA_NAME \ --display-name "SA_DISPLAY_NAME"
显示刚刚创建的服务账号的电子邮件地址。
gcloud iam service-accounts list
添加 Service Account Token Creator 角色。将
SA_EMAIL_ADDRESS
替换为服务账号的电子邮件地址。gcloud projects add-iam-policy-binding PROJECT_ID \ --member serviceAccount:SA_EMAIL_ADDRESS \ --role roles/iam.serviceAccountTokenCreator
在当前工作目录中创建服务账号密钥文件。 将
FILE_NAME
替换为您要用于密钥文件的名称。默认情况下,gcloud
命令会创建一个 JSON 文件。gcloud iam service-accounts keys create FILE_NAME.json \ --iam-account SA_EMAIL_ADDRESS
如需详细了解上述命令,请参阅 gcloud
参考。
如需了解如何对私钥采取保护措施,请参阅管理凭据的最佳做法。
配置 API 以支持身份验证
OpenAPI 文档中必须包含安全要求对象和安全性定义对象,ESP 才能验证签名的 JWT 中的声明。
在 OpenAPI 文档中将服务账号添加为颁发者。
securityDefinitions: DEFINITION_NAME: authorizationUrl: "" flow: "implicit" type: "oauth2" x-google-issuer: "SA_EMAIL_ADDRESS" x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/SA_EMAIL_ADDRESS"
- 将
DEFINITION_NAME
替换为标识此安全性定义的字符串。您可能希望将其替换为服务账号名称或标识调用服务的名称。 - 将
SA_EMAIL_ADDRESS
替换为服务账号的电子邮件地址。 - 您可以在 OpenAPI 文档中确立多个安全性定义,但每个定义必须具有不同的
x-google-issuer
。如果您为每个调用服务创建了单独的服务账号,那么您可以为每个服务账号创建安全性定义,例如:
securityDefinitions: service-1: authorizationUrl: "" flow: "implicit" type: "oauth2" x-google-issuer: "service-1@example-project-12345.iam.gserviceaccount.com" x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/service-1@example-project-12345.iam.gserviceaccount.com" service-2: authorizationUrl: "" flow: "implicit" type: "oauth2" x-google-issuer: "service-2@example-project-12345.iam.gserviceaccount.com" x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/service-2@example-project-12345.iam.gserviceaccount.com"
- 将
您可以选择将
x-google-audiences
添加到securityDefinitions
部分。如果您不添加x-google-audiences
,则 ESP 要求 JWT 中的"aud"
(目标对象)声明采用https://SERVICE_NAME
格式,其中 SERVICE_NAME 是您已在 OpenAPI 文档的host
字段中配置的 Endpoints 服务名称,除非使用标志--disable_jwt_audience_service_name_check
。如果使用此标志且未指定x-google-audiences
,则 JWTaud
字段不会被选中。您可以选择将
x-google-jwt-locations
添加到securityDefinitions
部分。您可以使用此值指定自定义 JWT 位置。默认 JWT 位置是Authorization
标头(带有“Bearer”前缀)、X-Goog-Iap-Jwt-Assertion
标头或access_token
查询参数。注意:- 如果指定了
x-google-jwt-locations
,则 Endpoints 会忽略所有默认位置。 - 只有 ESPv2 支持
x-google-jwt-locations
。
- 如果指定了
在文件的顶层位置添加
security
部分(不缩进或嵌套)以应用于整个 API,或者在方法级层添加以应用于特定方法。如果同时在 API 级层和方法级层使用security
部分,则方法级层的设置将替换 API 级层的设置。security: - DEFINITION_NAME: []
- 将
DEFINITION_NAME
替换为您在securityDefinitions
部分中使用的名称。 如果您在
securityDefinitions
部分有多个定义,请将它们添加到security
部分,例如:security: - service-1: [] - service-2: []
- 将
部署更新的 OpenAPI 文档。将
OPENAPI_DOC
替换为 OpenAPI 文档的名称。gcloud endpoints services deploy OPENAPI_DOC
在 ESP 向您的 API 转发请求之前,ESP 会验证以下几个方面:
- 使用公钥来验证 JWT 的签名,该签名位于 OpenAPI 文档的
x-google-jwks_uri
字段中指定的 URI 处。 - 验证 JWT 中的
"iss"
(颁发者)声明与x-google-issuer
字段中指定的值是否匹配。 - 验证 JWT 中的
"aud"
(目标对象)声明是否包含您的 Endpoints 服务名称或与您在x-google-audiences
字段中指定的任一值匹配。 - 使用
"exp"
(到期时间)声明来验证令牌是否未到期。
如需详细了解 x-google-issuer
、x-google-jwks_uri
、x-google-audiences
和 x-google-jwt-locations
,请参阅OpenAPI 扩展程序。
向 Endpoints API 发出经过身份验证的请求
要发送经过身份验证的请求,调用服务会发送用您在 OpenAPI 文档中指定的服务账号签名的JWT。调用服务必须:
- 创建 JWT 并使用服务账号的私钥对其进行签名。
- 将签名的 JWT 随请求发送到 API。
以下示例代码演示了此过程如何选取语言。如要使用其他语言发出经过身份验证的请求,请参阅 jwt.io 以获取支持的库列表。
- 在调用服务中,添加以下函数并将以下参数传递给它:
Java saKeyfile
:服务账号私钥文件的完整路径。saEmail
:服务账号的电子邮件地址。-
audience
:如果您将x-google-audiences
字段添加到了 OpenAPI 文档,请将audience
设置为您为x-google-audiences
指定的值之一。否则,请将audience
设置为https://SERVICE_NAME
,其中SERVICE_NAME
是 Endpoints 服务名称。 expiryLength
:JWT 的到期时间,以秒为单位。
Python -
sa_keyfile
:服务账号私钥文件的完整路径。 -
sa_email
:服务账号的电子邮件地址。 -
audience
:如果您将x-google-audiences
字段添加到了 OpenAPI 文档,请将audience
设置为您为x-google-audiences
指定的值之一。否则,请将audience
设置为https://SERVICE_NAME
,其中SERVICE_NAME
是 Endpoints 服务名称。 expiry_length
:JWT 的到期时间,以秒为单位。
Go saKeyfile
:服务账号私钥文件的完整路径。-
saEmail
:服务账号的电子邮件地址。 -
audience
:如果您将x-google-audiences
字段添加到了 OpenAPI 文档,请将audience
设置为您为x-google-audiences
指定的值之一。否则,请将audience
设置为https://SERVICE_NAME
,其中SERVICE_NAME
是 Endpoints 服务名称。 expiryLength
:JWT 的到期时间,以秒为单位。
该函数会创建一个 JWT,使用私钥文件对其进行签名,并返回已签名的 JWT。
Java Python Go - 在调用服务中,添加以下函数,将签名的 JWT 随请求的
Authorization: Bearer
标头发送到 API:Java Python Go
当您使用 JWT 发送请求时,出于安全原因,我们建议您将身份验证令牌放在 Authorization: Bearer
标头中。例如:
curl --request POST \ --header "Authorization: Bearer ${TOKEN}" \ "${ENDPOINTS_HOST}/echo"
其中 ENDPOINTS_HOST
和 TOKEN
分别是包含 API 主机名和身份验证令牌的环境变量。
在 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。