Android IAP的Java服务器端验证

2024-02-19

我想通过我的中央游戏服务器上的 Google API 验证 Android IAP。

有很多关于此的部分信息,这让我大吃一惊。 我还没有支付 25 欧元成为 Google 开发者,因为我不确定我是否能够让它发挥作用。

进行 IAP 时,会返回一个 JSON 对象。该对象包含多个字段,例如purchaseTokenproductId (source https://developer.android.com/google/play/billing/billing_reference.html).

我发现您可以通过以下 GET 请求来请求有关已购买产品的信息:GET https://www.googleapis.com/androidpublisher/v2/applications/packageName/purchases/products/productId/tokens/token.

我可以编程,没问题,但你需要自己授权:“此请求需要以下范围的授权” (source https://developers.google.com/android-publisher/api-ref/purchases/products/get)。 这就是我开始感到困惑的地方。

  1. 您需要通过开发控制台创建某种登录令牌(Link https://console.developers.google.com)。我不知道什么类型。OAuth 还是服务帐户?
  2. 这个令牌的寿命很短。你需要刷新一下

在互联网上可以找到几个巨大的代码片段,它们可能有效也可能无效,但它们都是部分的并且没有很好的文档记录。

我找到了 Google 的 Java API 库:link https://developers.google.com/api-client-library/java/。这个 API 似乎是为了为您解决 OAuth 和令牌的所有这些问题而设计的。但是,我无法弄清楚如何让这个 API 工作。

这可能并不难,但是有很多不同的方法可以做到这一点,而且我找不到任何明确的例子。

TL;DR:我需要验证 Google Play IAP 服务器端。为此,我想使用 Google Java API。

编辑:这可能是一个更简单的解决方案。将原始 JSON 加上 JSON 传递到服务器可能会更容易,因为我可以验证非对称签名服务器端。


我已经在 Scala 中做到了这一点,但是使用了 Java 标准库。我相信将该代码转换为 Java 应该很简单。该实现的主要优点是它对 Google 库的依赖为零。

  • 首先,您需要一个服务帐户。您可以通过 Google Dev 控制台创建它。它基本上会返回一个生成的电子邮件帐户,您将使用该帐户来验证后端服务并生成令牌。

  • 创建该帐户后,系统会提示您下载私钥。您需要它才能签署 JWT。

  • 您必须按照 Google 指定的格式生成 JWT(我在下面的代码中向您展示了如何操作)。 看:https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatingjwt https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatingjwt

  • 然后,使用 JWT,您可以请求访问令牌

  • 使用访问令牌,您可以发出请求来验证您的购买

/** Generate JWT(JSON Web Token) to request access token
* How to generate JWT: https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatingjwt
*
* If we need to generate a new Service Account in the Google Developer Console,
* we are going to receive a .p12 file as the private key. We need to convert it to .der.
* That way the standard Java library can handle that.
*
* Covert the .p12 file to .pem with the following command:
* openssl pkcs12 -in <FILENAME>.p12 -out <FILENAME>.pem -nodes
*
* Convert the .pem file to .der with the following command:
* openssl pkcs8 -topk8 -inform PEM -outform DER -in <FILENAME>.pem -out <FILENAME>.der -nocrypt
*
* */
private def generateJWT(): String = {

  // Generating the Header
  val header = Json.obj("alg" -> "RS256", "typ" -> "JWT").toString()

  // Generating the Claim Set
  val currentDate = DateTime.now(DateTimeZone.UTC)
  val claimSet =Json.obj(
    "iss" -> "<YOUR_SERVICE_ACCOUNT_EMAIL>",
    "scope" -> "https://www.googleapis.com/auth/androidpublisher",
    "aud" -> "https://www.googleapis.com/oauth2/v4/token",
    "exp" -> currentDate.plusMinutes(5).getMillis / 1000,
    "iat" -> currentDate.getMillis / 1000
  ).toString()

  // Base64URL encoded body
  val encodedHeader = Base64.getEncoder.encodeToString(header.getBytes(StandardCharsets.UTF_8))
  val encodedClaimSet = Base64.getEncoder.encodeToString(claimSet.getBytes(StandardCharsets.UTF_8))

  // use header and claim set as input for signature in the following format:
  // {Base64url encoded JSON header}.{Base64url encoded JSON claim set}
  val jwtSignatureInput = s"$encodedHeader.$encodedClaimSet"
  // use the private key generated by Google Developer console to sign the content. 
  // Maybe cache this content to avoid unnecessary round-trips to the disk.
  val keyFile = Paths.get("<path_to_google_play_store_api.der>");
  val keyBytes = Files.readAllBytes(keyFile);

  val keyFactory = KeyFactory.getInstance("RSA")
  val keySpec = new PKCS8EncodedKeySpec(keyBytes)
  val privateKey = keyFactory.generatePrivate(keySpec)

  // Sign payload using the private key
  val sign = Signature.getInstance("SHA256withRSA")
  sign.initSign(privateKey)
  sign.update(jwtSignatureInput.getBytes(StandardCharsets.UTF_8))
  val signatureByteArray = sign.sign()
  val signature = Base64.getEncoder.encodeToString(signatureByteArray)

  // Generate the JWT in the following format:
  // {Base64url encoded JSON header}.{Base64url encoded JSON claim set}.{Base64url encoded signature}
  s"$encodedHeader.$encodedClaimSet.$signature"
}

现在您已经生成了 JWT,您可以请求access token像那样:

/** Request the Google Play access token */
private def getAccessToken(): Future[String] = {

  ws.url("https://www.googleapis.com/oauth2/v4/token")
    .withHeaders("Content-Type" -> "application/x-www-form-urlencoded")
    .post(
      Map(
        "grant_type" -> Seq("urn:ietf:params:oauth:grant-type:jwt-bearer"),
        "assertion" -> Seq(generateJWT()))
    ).map {
    response =>
      try {
        (response.json \ "access_token").as[String]
      } catch {
        case ex: Exception => throw new IllegalArgumentException("GooglePlayAPI - Invalid response: ", ex)
      }
  }

}

有了访问令牌,您就可以自由验证您的购买。

我希望这有帮助。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android IAP的Java服务器端验证 的相关文章

随机推荐