适用于旧版捆绑服务的 App Identity API

区域 ID

REGION_ID 是 Google 根据您在创建应用时选择的区域分配的缩写代码。此代码不对应于国家/地区或省,尽管某些区域 ID 可能类似于常用国家/地区代码和省代码。对于 2020 年 2 月以后创建的应用,REGION_ID.r 包含在 App Engine 网址中。对于在此日期之前创建的现有应用,网址中的区域 ID 是可选的。

详细了解区域 ID

借助 App Identity API,应用能够找到其应用 ID(也称为项目 ID)。App Engine 应用可使用该 ID 向其他 App Engine 应用、Google API 以及第三方应用和服务声明其身份。应用 ID 也可用于生成网址或电子邮件地址,或者用于在运行时做出决策。

获取项目 ID

您可以使用 ApiProxy.getCurrentEnvironment().getAppId() 方法查找项目 ID。

获取应用主机名

默认情况下,App Engine 应用通过 https://PROJECT_ID.REGION_ID.r.appspot.com 形式的网址提供,其中项目 ID 是主机名的一部分。如果应用是通过自定义网域提供的,则可能需要检索整个主机名的组成部分。您可以使用 CurrentEnvironmentcom.google.appengine.runtime.default_version_hostname 属性执行此操作。

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  resp.setContentType("text/plain");
  ApiProxy.Environment env = ApiProxy.getCurrentEnvironment();
  resp.getWriter().print("default_version_hostname: ");
  resp.getWriter()
      .println(env.getAttributes().get("com.google.appengine.runtime.default_version_hostname"));
}

向其他 App Engine 应用声明身份

如果要确定向您的 App Engine 应用发出请求的 App Engine 应用的身份,则可以使用请求标头 X-Appengine-Inbound-Appid。此标头由 URLFetch 服务添加到请求中,而且用户无法修改,因此它安全地标明了发出请求的应用的项目 ID(如果存在)。

要求

  • 只有对应用的 appspot.com 网域进行的调用才包含 X-Appengine-Inbound-Appid 标头。对自定义网域的调用不包含该标头。
  • 您的请求必须设置为不遵循重定向。如果您使用 URLFetchService 类,则您的应用必须指定 doNotFollowRedirect。默认情况下,在 Java 8 运行时中运行的应用不使用 URLFetch 服务。如需启用 URLFetch 服务,请按照以下说明操作
  • 如果您的应用使用 java.net,请将您的代码更新为不遵循重定向:
    connection.setInstanceFollowRedirects(false);

在应用处理程序中,您可以通过读取 X-Appengine-Inbound-Appid 标头并将其与允许发出请求的 ID 列表进行比对来检查传入 ID。

向 Google API 声明身份

Google API 使用 OAuth 2.0 协议进行身份验证和授权。App Identity API 可以创建 OAuth 令牌,这些令牌可用于声明请求的来源是应用本身。getAccessToken() 方法会返回一个范围或范围列表的访问令牌。然后,可以在调用的 HTTP 标头中设置此令牌,以识别调用应用。

以下示例说明了如何使用 App Identity API 对 Google URL Shortener API 进行 REST 调用。

/**
 * Returns a shortened URL by calling the Google URL Shortener API.
 *
 * <p>Note: Error handling elided for simplicity.
 */
public String createShortUrl(String longUrl) throws Exception {
  ArrayList<String> scopes = new ArrayList<>();
  scopes.add("https://www.googleapis.com/auth/urlshortener");
  final AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
  final AppIdentityService.GetAccessTokenResult accessToken = appIdentity.getAccessToken(scopes);
  // The token asserts the identity reported by appIdentity.getServiceAccountName()
  JSONObject request = new JSONObject();
  request.put("longUrl", longUrl);

  URL url = new URL("https://www.googleapis.com/urlshortener/v1/url?pp=1");
  HttpURLConnection connection = (HttpURLConnection) url.openConnection();
  connection.setDoOutput(true);
  connection.setRequestMethod("POST");
  connection.addRequestProperty("Content-Type", "application/json");
  connection.addRequestProperty("Authorization", "Bearer " + accessToken.getAccessToken());

  OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
  request.write(writer);
  writer.close();

  if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
    // Note: Should check the content-encoding.
    //       Any JSON parser can be used; this one is used for illustrative purposes.
    JSONTokener responseTokens = new JSONTokener(connection.getInputStream());
    JSONObject response = new JSONObject(responseTokens);
    return (String) response.get("id");
  } else {
    try (InputStream s = connection.getErrorStream();
        InputStreamReader r = new InputStreamReader(s, StandardCharsets.UTF_8)) {
      throw new RuntimeException(
          String.format(
              "got error (%d) response %s from %s",
              connection.getResponseCode(), CharStreams.toString(r), connection.toString()));
    }
  }
}

请注意,应用的身份由服务账号名称表示,通常为 applicationid@appspot.gserviceaccount.com。您可以使用 getServiceAccountName() 方法获取确切的值。对于提供 ACL 的服务,您可以通过向此账号授予访问权限来向应用授予访问权限。

向第三方服务声明身份

getAccessToken() 生成的令牌仅适用于 Google 服务。但是,您可以使用基础签名技术向其他服务声明应用的身份。signForApp() 方法将使用应用特有的私钥为字节签名,并且 getPublicCertificatesForApp() 方法将返回可用于验证签名的证书。

下列示例说明了如何对 blob 签名并验证其签名:
// Note that the algorithm used by AppIdentity.signForApp() and
// getPublicCertificatesForApp() is "SHA256withRSA"

private byte[] signBlob(byte[] blob) {
  AppIdentityService.SigningResult result = appIdentity.signForApp(blob);
  return result.getSignature();
}

private byte[] getPublicCertificate() throws UnsupportedEncodingException {
  Collection<PublicCertificate> certs = appIdentity.getPublicCertificatesForApp();
  PublicCertificate publicCert = certs.iterator().next();
  return publicCert.getX509CertificateInPemFormat().getBytes("UTF-8");
}

private Certificate parsePublicCertificate(byte[] publicCert)
    throws CertificateException, NoSuchAlgorithmException {
  InputStream stream = new ByteArrayInputStream(publicCert);
  CertificateFactory cf = CertificateFactory.getInstance("X.509");
  return cf.generateCertificate(stream);
}

private boolean verifySignature(byte[] blob, byte[] blobSignature, PublicKey pk)
    throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
  Signature signature = Signature.getInstance("SHA256withRSA");
  signature.initVerify(pk);
  signature.update(blob);
  return signature.verify(blobSignature);
}

private String simulateIdentityAssertion()
    throws CertificateException, UnsupportedEncodingException, NoSuchAlgorithmException,
    InvalidKeyException, SignatureException {
  // Simulate the sending app.
  String message = "abcdefg " + Calendar.getInstance().getTime().toString();
  byte[] blob = message.getBytes();
  byte[] blobSignature = signBlob(blob);
  byte[] publicCert = getPublicCertificate();

  // Simulate the receiving app, which gets the certificate, blob, and signature.
  Certificate cert = parsePublicCertificate(publicCert);
  PublicKey pk = cert.getPublicKey();
  boolean isValid = verifySignature(blob, blobSignature, pk);

  return String.format(
      "isValid=%b for message: %s\n\tsignature: %s\n\tpublic cert: %s",
      isValid, message, Arrays.toString(blobSignature), Arrays.toString(publicCert));
}

获取默认的 Cloud Storage 存储桶名称

每个应用都可以有一个默认的 Cloud Storage 存储分区,其中包括 5 GB 免费存储空间和免费的 I/O 操作配额

如需获取默认存储分区的名称,您可以使用 App Identity API。请调用 AppIdentityService.getDefaultGcsBucketName