Google Play Billing安全:无developerPayload字段时如何验证购买令牌归属特定用户?
developerPayload字段的情况下证明购买令牌属于特定用户 你提到的这个问题确实很关键——虽然Google现在推荐用账号凭证做后端验证,但在不安全的网络环境下,客户端直接传递的账号信息确实有被篡改的风险。下面是几个经过实践验证的方案,可以帮你建立购买令牌和特定用户之间的可信关联:
1. 关联Google Play账号的可信标识
客户端可以通过Google Sign-In获取当前登录的Google账号的ID Token或Profile ID,在购买完成后,将这个标识和购买令牌一起传给你的后端。
后端在调用Google Play Billing API验证购买时,可以检查接口返回的purchase.userProfile.email或purchase.profileId,和客户端传递的账号标识是否匹配。这样就能确保购买令牌确实属于该Google账号的用户。
注意:要在发起购买前确认当前Google Play账号和用户登录的账号一致,避免用户中途切换账号导致的关联错误。
2. 服务器端生成一次性验证令牌
在用户发起购买流程前,先让客户端向后端请求一个唯一的一次性令牌(比如UUID)。客户端将这个令牌暂存后再发起购买,购买完成后,把一次性令牌、购买令牌以及用户的APP内ID一起传给后端。
后端的验证流程:
- 先检查一次性令牌是否有效(未被使用、未过期)
- 验证通过后,标记该令牌已使用,再调用Google Play的验证接口
- 最后将购买令牌和用户的APP内ID绑定
这种方式即使网络被劫持,一次性令牌只能使用一次,能有效防止伪造请求。
3. 利用obfuscatedAccountId和obfuscatedProfileId字段
Google Play Billing Library 5.0及以上版本支持在发起购买时设置obfuscatedAccountId和obfuscatedProfileId字段。你可以把用户在你的APP内的唯一ID(加密后)填入这两个字段,Google会将它们和购买记录绑定。
举个Kotlin的代码示例:
// 先对用户ID进行加密(比如用你的后端密钥) val encryptedUserId = encryptUserAppId(currentUser.id) val productDetailsParams = ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .setObfuscatedAccountId(encryptedUserId) .build() val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf(productDetailsParams)) .build() billingClient.launchBillingFlow(activity, billingFlowParams)
后端在调用Google Play验证接口时,会收到这两个加密后的字段,解密后就能直接和当前用户的APP内ID对比,确认购买令牌的归属。
4. 结合设备安全验证(辅助手段)
可以搭配Google SafetyNet Attestation来验证设备的完整性,同时获取设备的可信标识符(比如经过验证的ANDROID_ID)。将这些信息和购买令牌一起传给后端,作为辅助验证依据。
不过要注意:设备标识符可能会因恢复出厂设置等情况变化,所以不能单独依赖,最好和账号信息或APP内用户ID结合使用。
总结
最稳妥的方式是组合使用多种方案,比如Google账号标识 + obfuscatedAccountId,或者一次性令牌 + 加密用户ID,这样能最大程度降低网络劫持带来的风险,确保购买令牌和特定用户的绑定关系可信。
内容的提问来源于stack exchange,提问作者PSJay Peng

