API de App Identity para servicios agrupados en paquetes heredados

ID de región

REGION_ID es un código abreviado que Google asigna en función de la región que eliges cuando creas la app. El código no corresponde a un país ni a una provincia, aunque algunos ID de región puedan parecer similares a los códigos de país y provincia que se suelen usar. En el caso de las apps creadas después de febrero de 2020, REGION_ID.r se incluye en las URL de App Engine. En el caso de las apps existentes creadas antes de esta fecha, el ID de región es opcional en la URL.

Obtén más información acerca de los ID de región.

La API de App Identity permite que una aplicación descubra su ID de aplicación (también llamado ID del proyecto). A través del ID, una aplicación de App Engine puede confirmar su identidad a otras aplicaciones de App Engine, a las API de Google, y a las aplicaciones y servicios de terceros. El ID de aplicación también se puede usar para generar una URL o dirección de correo electrónico, o tomar una decisión en tiempo de ejecución.

Obtén el ID del proyecto

El ID del proyecto se puede encontrar mediante el método ApiProxy.getCurrentEnvironment().getAppId().

Obtén el nombre de host de la aplicación

De forma predeterminada, las apps de App Engine se entregan desde las URL en el formato https://PROJECT_ID.REGION_ID.r.appspot.com, en el que el ID del proyecto forma parte del nombre de host. Si una app se entrega desde un dominio personalizado, tal vez sea necesario recuperar todo el componente del nombre de host. Puedes hacerlo mediante el atributo com.google.appengine.runtime.default_version_hostname del CurrentEnvironment.

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

Confirma la identidad a otras apps de App Engine

Si deseas determinar la identidad de la app de App Engine que realiza una solicitud a tu app de App Engine, puedes usar el encabezado de solicitud X-Appengine-Inbound-Appid. El servicio URLFetch agrega este encabezado a la solicitud y el usuario no puede modificarlo, por lo que indica de manera segura el ID del proyecto de la aplicación solicitante, si está presente.

Requisitos:

  • Solo las llamadas realizadas al dominio appspot.com de la app tendrán el encabezado X-Appengine-Inbound-Appid. Las llamadas a dominios personalizados no contienen el encabezado.
  • Las solicitudes deben configurarse para no seguir redireccionamientos. Si usas la clase URLFetchService, la app debe especificar doNotFollowRedirect. Las apps que se ejecutan en el entorno de ejecución de Java 8 no usan el servicio URLFetch de forma predeterminada. Para habilitar URLFetch, sigue estas instrucciones:
  • Si la app usa java.net, actualiza el código para que no sigas los redireccionamientos:
    connection.setInstanceFollowRedirects(false);

En el controlador de la aplicación, puedes verificar el ID entrante si lees el encabezado X-Appengine-Inbound-Appid y lo comparas con una lista de ID permitidos para realizar solicitudes.

Confirma la identidad a las API de Google

Las API de Google usan el protocolo OAuth 2.0 para la autenticación y la autorización. La API de App Identity puede crear tokens de OAuth que se pueden usar para confirmar que el origen de una solicitud es la misma aplicación. El método getAccessToken() muestra un token de acceso para un permiso o una lista de permisos. Este token se puede configurar en los encabezados HTTP de una llamada para identificar la aplicación que realiza la llamada.

En el ejemplo siguiente, se muestra cómo usar la API de App Identity para realizar una llamada REST a la API de Reductor de URL de Google.

/**
 * 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()));
    }
  }
}

Ten en cuenta que el nombre de la cuenta de servicio representa la identidad de la aplicación, el cual suele ser applicationid@appspot.gserviceaccount.com. Puedes obtener el valor exacto mediante el método getServiceAccountName(). Para los servicios que ofrecen LCA, puedes otorgar acceso a la aplicación si se lo otorgas a esta cuenta.

Confirma la identidad a servicios de terceros

El token que genera getAccessToken() solo funciona en los servicios de Google. Sin embargo, puedes usar la tecnología de firma subyacente para confirmar la identidad de tu aplicación a otros servicios. El método signForApp() firmará bytes mediante una clave privada única para la aplicación, y el método getPublicCertificatesForApp() mostrará los certificados que se pueden usar a fin de validar la firma.

A continuación, puedes ver un ejemplo que muestra cómo firmar un BLOB y validar su firma:
// 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));
}

Obtén el nombre predeterminado del bucket de Cloud Storage

Cada aplicación puede tener un bucket de Cloud Storage predeterminado, que incluye 5 GB de almacenamiento gratuito y una cuota gratuita para operaciones de E/S.

Para obtener el nombre del bucket predeterminado, puedes usar la API de App Identity. Llama a AppIdentityService.getDefaultGcsBucketName.