安全与风控
在 Android 上加固 RevenueCat…
适用于审查和加固 Android 端 RevenueCat 集成,防止伪造 CustomerInfo、secret key 泄露、匿名身份冒充和客户端越权访问付费内容。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 Android 上加固 Revenu特定
解决思路
- 先声明审查范围:在 Android 上检查 RevenueCat 的安全状态,包括验证模式、API Key、用户身份和服务端访问决策。
- 确认 RevenueCat 已自动处理的事项,以聚焦真实缺口:Google Play 收据校验由 RevenueCat 后端在 awaitPurchase() 返回前执行;购买 token 复用由后端去重;伪造购买 token 会在 Google Play 校验失败且不授予权益;SDK 始终使用 HTTPS 传输到 RevenueCat;网络失败后的 token 重传由 SDK 在下次应用启动时重试。
- 询问项目是否设置了 EntitlementVerificationMode;如果没有,CustomerInfo 响应会在未做签名检查的情况下被信任。
- 询问项目 SDK 配置的是公开 Android key(以 goog_ 开头),还是有人误把 secret key 粘贴进了客户端。
- 询问项目真实用户是否通过 Purchases.logIn(yourUserId) 识别,还是仍依赖匿名 $RCAnonymousID:...。
- 询问项目提供 premium 内容的 server endpoints 是否在服务端验证权益,还是信任客户端传来的 flag。
- 选择权益验证模式:DISABLED 为默认,不进行签名;INFORMATIONAL 记录验证失败但仍授予访问;ENFORCED 在验证失败时让 EntitlementInfo.isActive 返回 false。
- 若想获得信号但不想因误判拒绝真实用户,选择 INFORMATIONAL。
- 若接受代理或 VPN 导致的一些 false negatives,以换取严格的客户端保证,选择 ENFORCED。
- 决定权威来源:每个 premium endpoint 要么每次请求调用 RevenueCat REST API,要么读取由 RevenueCat webhooks 驱动的本地 has_entitlement flag。
- 为权威来源选定一种方案并记录;没有理由不要在不同 endpoint 混用。
- 若应用已上线,计划使用你自己的已认证 user id 调用 Purchases.logIn("your_user_id");匿名 id 是设备级标识,不是凭据,并且会被同一设备上的多个用户共享。
- 打开响应验证:在 PurchasesConfiguration 中加入 .entitlementVerificationMode(EntitlementVerificationMode.INFORMATIONAL)。
- 检查权益时读取验证结果:根据 customerInfo.entitlements.verification 处理 VerificationResult.VERIFIED(响应真实)、FAILED(可能篡改,记录并告警)、NOT_REQUESTED(验证被禁用)、VERIFIED_ON_DEVICE(本地验证通过)。
- 在有遥测确认真实流量中 FAILED 很少后,升级为 .entitlementVerificationMode(EntitlementVerificationMode.ENFORCED)。
- 在 ENFORCED 下,签名验证失败会把受影响 entitlement 的 isActive 翻转为 false。
- 正确放置 API Key:公开 Android SDK key(goog_...)嵌入应用二进制;secret REST API key 放在服务器、secret manager 或环境变量。
- 公开 key 只能为调用用户读取和购买;secret key 拥有完整 REST API、管理操作和授予权益能力。
- 在 Android 代码中只出现公开 key:Purchases.configure(PurchasesConfiguration.Builder(context, "goog_PUBLIC_android_sdk_key").build())。
- 绝不把 secret key 提交到 app repo。
- 发布前 grep Android 源码树中的 secret key 前缀,确认零命中。
- 一旦有已认证 user id 就调用 logIn:val result = Purchases.sharedInstance.awaitLogIn("your_user_id"); val customerInfo = result.customerInfo。
- 不要把匿名 id 当作凭据;它是设备级标识,不能保护共享设备上的购买历史。
- 即使启用 ENFORCED,也应在服务端而非客户端执行强制校验:每个提供付费内容的 server endpoint 都在服务端检查权益。
- 服务端校验方式一:调用 RevenueCat REST API,例如 get_premium_content(user_id) 中 revenuecat.get_subscriber(user_id),若 not info.entitlements["pro"].is_active 则 raise Forbidden(),否则返回 content。
- 服务端校验方式二:读取由 RevenueCat webhooks 驱动的本地 has_pro flag,并在每次请求检查该 flag。
- 验证 Android 源码只包含公开 SDK key;secret key grep 干净。
- 验证 EntitlementVerificationMode 已设置(不是 DISABLED),并监控 VerificationResult.FAILED 遥测。
- 验证真实用户通过 Purchases.logIn 识别;匿名 id 仅用于 pre login 流程。
- 验证服务器上每个付费内容 endpoint 都通过 REST API 或 webhook 驱动的 flag 验证权益;没有任何 endpoint 信任客户端 header。
- 修复常见错误:app 中只放公开 key,secret key 只放服务端,避免攻击者反编译 APK 后调用管理 REST endpoints。
- 修复常见错误:生产环境不要把模式留在 DISABLED;设置为 INFORMATIONAL 或 ENFORCED,防止中间人伪造 CustomerInfo 响应。
- 修复常见错误:不要将匿名 id 当作凭据;用你的已认证 user id 调用 logIn。
- 修复常见错误:服务端不要信任来自客户端的 customerInfo;通过 REST API 或 webhook 驱动的 DB 在服务端验证。
适用边界 · 注意事项
- 不适用于非 Android 或非 RevenueCat 的支付/订阅集成;这些步骤依赖 RevenueCat SDK、后端和 REST API。
- 如果项目没有服务端付费内容端点,则不需要实现服务端权益校验,但仍需处理客户端验证和 API Key。
- 若无法承受对代理/VPN 用户的误拒,不要直接启用 ENFORCED;先用 INFORMATIONAL 收集遥测。