如何防范Android应用中同一Google账号关联多Firebase账号滥用订阅限额?
解决Google Play订阅与Firebase账号绑定的滥用问题
方案1:强制订阅Google账号与Firebase账号绑定
核心思路是让用户必须通过订阅对应的Google账号完成Firebase Google Sign-In登录,确保订阅与Firebase账号一一对应,从根源上杜绝跨账号滥用。
实现步骤:
- 订阅成功时绑定账号信息:用户完成订阅后,获取该订阅对应的Google账号唯一ID(可通过BillingClient的
Purchase对象或Firebase Auth的Google Provider数据获取),并将其与当前Firebase用户UID绑定存储到Firestore。 - 登录时验证账号匹配:每次用户登录Firebase后,检查当前账号关联的Google ID是否与订阅记录中的Google ID一致,不一致则拒绝访问高级功能。
- 限额关联到Google账号:将使用限额数据以Google账号ID为主键存储,而非Firebase UID,确保同一Google账号下的所有操作共享限额。
代码示例:
// 订阅成功后,绑定Google账号与Firebase账号 fun onSubscriptionSuccess(purchase: Purchase) { val firebaseUser = FirebaseAuth.getInstance().currentUser ?: return // 获取当前Firebase账号关联的Google账号ID val googleAccountId = firebaseUser.providerData .firstOrNull { it.providerId == "google.com" }?.uid ?: return // 将订阅信息与Google账号ID绑定存储到Firestore FirebaseFirestore.getInstance() .collection("subscriptions") .document(googleAccountId) .set(mapOf( "firebaseUid" to firebaseUser.uid, "purchaseToken" to purchase.purchaseToken, "isActive" to true, "expiryTime" to purchase.purchaseTime + purchase.subscriptionPeriodMillis )) } // 登录后验证订阅与账号匹配,并检查限额 fun validateSubscriptionAndLimit() { val params = QueryPurchasesParams.newBuilder() .setProductType(BillingClient.ProductType.SUBS) .build() billingClient.queryPurchasesAsync(params) { billingResult, purchases -> if (billingResult.responseCode == BillingResponseCode.OK) { val hasActiveSubscription = purchases.any { it.purchaseState == PurchaseState.PURCHASED && it.isAcknowledged } if (hasActiveSubscription) { val firebaseUser = FirebaseAuth.getInstance().currentUser ?: return@queryPurchasesAsync val userGoogleId = firebaseUser.providerData .firstOrNull { it.providerId == "google.com" }?.uid // 从Firestore获取订阅对应的Google账号ID FirebaseFirestore.getInstance() .collection("subscriptions") .whereEqualTo("purchaseToken", purchases.first().purchaseToken) .get() .addOnSuccessListener { documents -> val subscriptionGoogleId = documents.firstOrNull()?.id if (userGoogleId == subscriptionGoogleId) { // 账号匹配,检查并应用限额(以Google账号ID为键) checkDailyLimit(userGoogleId!!) } else { // 提示用户必须使用订阅的Google账号登录 } } } } } } // 检查并更新每日限额(关联Google账号ID) private fun checkDailyLimit(googleAccountId: String) { val today = LocalDate.now().toString() val limitDoc = FirebaseFirestore.getInstance() .collection("usage_limits") .document(googleAccountId) limitDoc.get().addOnSuccessListener { doc -> val currentUsage = doc.getLong("${today}_usage") ?: 0 if (currentUsage < DAILY_LIMIT) { // 允许使用,更新限额 limitDoc.update("${today}_usage", currentUsage + 1) // 执行高级功能逻辑 } else { // 提示超出每日限额 } } }
方案2:将使用限额直接关联到Google账号
如果不需要严格绑定Firebase账号,可直接将使用限额数据与Google账号ID关联——不管用户切换哪个Firebase账号,只要关联的是同一个Google账号,就共享同一限额。
实现要点:
- 取消限额与Firebase UID的绑定,所有限额操作均以Google账号ID为依据。
- 用户使用高级功能前,必须通过Google Sign-In关联Firebase账号,无关联则拒绝访问。
- 定期通过Google Play Developer API验证订阅状态,避免客户端篡改。
方案3:限制高级功能仅对Google Sign-In用户开放
完全禁止邮箱/密码等非Google Sign-In方式的Firebase账号访问高级功能,确保所有订阅用户都通过Google账号登录,从根源上切断跨账号滥用的可能。
实现步骤:
- 在应用内区分登录方式,仅允许Google Sign-In用户进入高级功能界面。
- 当非Google Sign-In用户尝试访问高级功能时,引导其切换到订阅对应的Google账号登录。
关键最佳实践
- 服务器端验证订阅:不要仅依赖客户端检查,通过Firebase Cloud Functions或自有后端调用Google Play Developer API验证Purchase Token的有效性,防止客户端伪造订阅状态。
- 定期同步订阅状态:每日或每次应用启动时,同步订阅状态到Firestore,确保状态实时更新。
- 账号关联限制:在Firestore中维护Google账号ID与Firebase UID的映射,禁止同一Google账号关联多个Firebase账号,若检测到新关联,提示用户合并数据或使用原有账号。
内容的提问来源于stack exchange,提问作者Yauheni Mokich
相关产品推荐
相关产品推荐

