Android IAP的Java服务器端验证

时间:2016-10-14 13:36:08

标签: java android oauth in-app-purchase

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

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

创建IAP时,将返回JSON对象。此对象包含多个字段,例如purchaseTokenproductIdsource)。

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

我可以对此进行编程没有问题,但您需要自行授权:"此请求需要具有以下范围的授权" source)。 这是我开始感到困惑的地方。

  1. 您需要通过开发者控制台(Link)创建某种登录令牌。我不知道什么类型。 OAuth或服务帐户?
  2. 此令牌是短暂的。你需要刷新它
  3. 互联网上有几个巨大的代码片段可能会或可能不会起作用,但它们都是部分的,并没有很好的记录。

    我找到了适用于Java的Googles API库:link。此API似乎是为了解决OAuth和令牌的所有这些问题。但是,我无法弄清楚如何让这个API工作。

    这可能不是那么难,但有很多不同的方法可以做到,我找不到任何明确的例子。

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

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

2 个答案:

答案 0 :(得分:2)

我在Scala中完成了这项工作,但使用的是标准Java库。我认为将该代码转换为Java应该很简单。

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

  • 创建该帐户后,您将立即下载您的私钥。您需要这样才能签署JWT。

  • 您必须以Google指定的格式生成JWT。 请参阅: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 private key generated by Google Developer console to sign the content
        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)
            }
        }

      }

手上有访问令牌,您可以自由验证购买情况。

我希望有所帮助。

答案 1 :(得分:1)

继上述评论之后,Google API管理员的设计已经改变,但程序仍然相同。您想要创建服务帐户,创建后可以下载 JSON Web令牌,它是一个简单的JSON文件,其中包含您需要进行身份验证的凭据。该服务帐户应具有访问Google Play Developer API所需的所有权限。您需要在Google Play开发者控制台&gt;中授予对财务的访问权限。设置&gt; API访问页面。

您可以使用Google APIs Client Library for Java对Google Play Developer API进行身份验证和请求。按照OAuth2 Service accounts文档设置。 Data store上有一条说明,描述了如何处理刷新令牌。

还有关于如何使用Google Play Developer API Client Library for Java的文档。

对于验证购买JSON的签名,购买意图的响应有效负载中有INAPP_DATA_SIGNATURE。有关如何获取该文档的详细信息,请参阅Purchasing an Item上的文档。您可以通过base64验证INAPP_PURCHASE_DATA解码签名并使用您的许可密钥进行验证,该密钥可在Google Play开发者控制台&gt;中找到。所有应用程序&gt; [应用名称]&gt;服务与蜜蜂。确保INAPP_PURCHASE_DATA完好无损,否则您最终会得到this problem

希望这有帮助。