Play Integrity API 只回答一个很具体的问题:这次请求,是不是来自「Google Play 分发给你的那个应用」,跑在一台「确实是它自称的那台设备」上。你的服务器向 Google 要一份完整性判定(integrity verdict),拿到一段签名过的载荷,然后在服务器一侧——永远不要在客户端——决定这次交互可以信任到什么程度。它是拖慢篡改版、脚本化客户端、模拟器和旁路安装包的标准手段,拦在它们碰到你的游戏经济、订阅权益或广告收入之前。
下面按你实际动手的顺序展开:启用 API、关联 Cloud 项目、在标准请求与经典请求之间做选择、预热 token provider、请求并解密 token、读懂判定字段、处理配额与错误码,最后是灰度上线而不把付费用户挡在门外。贯穿全篇只有一条原则:这个 API 提供的是给你自己做决策的信号,而唯一能长期通过完整性检查的方式,就是走官方渠道发布一个真实的应用。
一、它在证明什么,又不证明什么
默认会返回三类判定,各自回答一个不同的问题。
- appIntegrity(应用完整性)——这是不是你未被改动的二进制包?
appRecognitionVerdict返回PLAY_RECOGNIZED表示包名与证书都和 Google Play 分发的一致;UNRECOGNIZED_VERSION表示证书或包名与 Play 记录对不上;UNEVALUATED表示缺少某项必要前提,没能评估。 - deviceIntegrity(设备完整性)——设备是不是真的?默认情况下
deviceRecognitionVerdict可能是MEETS_DEVICE_INTEGRITY:一台真实且通过认证的 Android 设备,在 Android 13 及以上还有硬件级证明,表明引导程序已锁定、加载的系统为真。如果一台设备不满足任何标签,这个字段会被整个省略。 - accountDetails(账号权益)——用户是否持有权益?
appLicensingVerdict返回LICENSED(从 Google Play 安装或更新)、UNLICENSED或UNEVALUATED。
比字段清单更重要的有两点。第一,Google 明确写着:这个 API 最好与其他信号配合使用,作为整体反滥用策略的一部分,而不是唯一手段——判定只是一个输入,把它当成唯一闸门,既会产生误伤,也容易被绕过。第二,这些是风险信号,不是对用户的定性:一台已 root 的设备上可能只是一个爱折腾的玩家,你的处置力度应当与那个页面上的实际利害相匹配。
二、第一步:启用 API 并关联 Cloud 项目
每一个调用该 API 的应用或 SDK 都需要一个 Google Cloud 项目,因为用量统计和配额管理都在那里。
- 在 Google Cloud Console 新建一个项目,或选择你要使用的既有项目。
- 进入 APIs and services → Enable APIs and services,搜索 Play Integrity API,点击 Enable。
- 把这个项目关联到你的应用。打开 Play Console 中你的应用,进入 Protected with Play,找到 Play Integrity API,点击 Get started。
「关联」这一步才是解锁后续能力的关键。只在 Cloud 里启用、却没有在 Play Console(SDK 则在 Play SDK Console)里关联的项目,拿不到额外的配置选项,也无法申请提高配额。如果你跳过了上面第二步,关联流程会顺带替你启用 API,所以两种顺序都可行;但跳过关联不行。
解密需要一份绝不能放进客户端的凭据。请在已关联的 Cloud 项目里创建一个服务账号,在服务器上用 playintegrity 作用域调用解密接口。客户端只负责申请 token,只有服务器负责打开它。
三、第二步:选标准请求还是经典请求
有两套请求方式,一开始就选对,可以省掉一次返工。
- 标准请求(standard request)是 Google Play 应用的默认选择,支持 Android 5.0(API 21)及以上,由两次调用构成:先提前准备好 integrity token provider,再在真正需要的时候请求一个 token。
- 经典请求(classic request)用于标准请求覆盖不到的场景,最典型的是「完全在 Google Play 之外分发」的应用,以及 SDK。经典请求结构上是单次调用,传入你自己生成的
nonce;不在 Play 分发的应用还必须用setCloudProjectNumber()带上自己的 Cloud 项目编号,而在 Play 上的应用是通过控制台关联的,无需在请求里传。
应用在 Google Play 上,就选标准请求。只有在确实于 Play 之外分发、或某个具体集成限制了你的时候,才用经典请求。
四、第三步:先预热 provider,再请求 token
标准请求被刻意拆成两段,目的就是让慢的那一段永远不落在用户的关键路径上。
准备(预热)integrity token provider。要在真正需要判定之前就调用,通常放在应用启动时——这个调用是异步的,不会拖慢启动。预热让 Google Play 在设备上缓存部分证明信息,使之后那次请求更快;重新预热也是一种更轻量的方式,让下一次判定更新。provider 持有太久会失效,表现为 INTEGRITY_TOKEN_PROVIDER_INVALID,这时应当重新申请一个 provider,而不是拿旧的反复重试。
按需请求 integrity token。当应用要发起一个你希望担保的服务器请求时,申请一个 token 并发给后端。请带上 request hash:把这次请求的稳定参数做一个摘要(例如 SHA-256)传进去,因为 API 会把这个值原样放进签名响应里回传。没有它,token 只绑定到设备、不绑定到具体这次请求,而这正是中间代理把它挪作他用所需要的缺口。
经典请求则改用你自己提供的 nonce。它会原样传给 Google,所以不要放任何个人或敏感信息;同时它必须是长度合规的合法 Base64,否则你收到的不是判定,而是 NONCE_TOO_SHORT、NONCE_TOO_LONG 或 NONCE_IS_NOT_BASE64。
五、第四步:在服务器上解密并校验
抵达你后端的 token 是加密的,在 Google 解密之前毫无用处。用服务账号凭据与 playintegrity 作用域,你的服务器调用:
POST https://playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken
{ "integrity_token": "INTEGRITY_TOKEN" }
返回的是一段纯文本 JSON 载荷。在看任何一项判定之前,先校验请求信息,因为只有当它确实是你这次请求的结果时,载荷才有意义:
requestPackageName必须等于你的包名;requestHash必须等于你发出的那个哈希;timestampMillis必须足够新——与一个允许的时效窗口(毫秒级的 allowed window)比较,超期一律拒绝,因为过期的 token 就是重放攻击的候选。
Google Play 还提供自动重放保护,阻止同一个 integrity token 被反复使用。请把它当作一层安全网,而不是你自己时效校验的替代品;也正因如此,应当在动作发生的那一刻在服务器端解密,而不是缓存一整天。
六、读懂载荷,同时避免过度拦截
载荷是一个 JSON,包含 requestDetails、appIntegrity、deviceIntegrity、accountDetails,以及在你选择开启后出现的 environmentDetails。字段顺序不保证,务必按 key 读取。一个可用的判定顺序大致如下。
- 先确认应用本身:
PLAY_RECOGNIZED表示二进制与证书就是 Play 分发的那一份。 - 再看设备:
MEETS_DEVICE_INTEGRITY是默认门槛。其余标签——MEETS_BASIC_INTEGRITY、MEETS_STRONG_INTEGRITY、MEETS_VIRTUAL_INTEGRITY——需要另行开启,属于分级策略,不该出现在第一版里。 - 在权益相关的地方使用
accountDetails:UNLICENSED表示该账号没有 Play 权益;但要注意,在部分较老的设备上,卸载后权益可能仍然保留,所以它更适合当作信号而非事实。 - 若你开启了
environmentDetails,其中的appAccessRiskVerdict会告诉你设备上是否存在具备截屏或悬浮窗能力的应用——它的appsDetected列表使用KNOWN_INSTALLED、UNKNOWN_INSTALLED、UNKNOWN_CAPTURING之类的取值——Play Protect 判定则概括了设备的 Play Protect 状态。
有两个不那么直观的情况值得提前设计。能通过系统完整性检查、满足 Android 核心兼容要求的模拟器,也可能拿到属于它自己的设备标签,所以「模拟器」并不自动等于「攻击者」。而一台不满足任何标签的设备会直接省略该字段,这意味着假设这个 key 永远存在的代码,会以崩溃收场,而不是走向「安全拒绝」。
七、配额、错误码,以及如何安全地失败
默认情况下,你的应用每天在所有安装上合计最多可以发起 10,000 次请求。上线之后这条天花板比团队预期的更快到来,所以要在已关联的 Cloud 项目里盯住用量,趁趋势线还有余量时提交提额申请。超限表现为 TOO_MANY_REQUESTS(错误码 -8),正确做法是指数退避重试,而不是把用户挡在门外。
错误处理正是集成最容易出错的地方。以下这些属于瞬时错误,应当退避重试:NETWORK_ERROR(-3)、TOO_MANY_REQUESTS(-8)、GOOGLE_SERVER_UNAVAILABLE(-12)、CLIENT_TRANSIENT_ERROR(-18)。另一些则是环境问题——没有 Play 商店、没有 Play 服务、Play 服务版本过旧、缺少 Play 账号、Cloud 项目编号被接口判定无效。对这类情况,Google 的建议是按「客户端未通过检查」来处置,同时尽可能保持交互可用。
把错误当成策略问题,而不只是代码分支。凡是设备看着不寻常就一律拦截,会把一个反滥用信号变成留存问题;反过来,对所有失败视而不见则是另一种错误。按页面分级决定——排行榜可以宽松,钱包充值可以严格——并把理由写下来,让接手的人知道为什么。
Google 还提供了补救对话框(remediation dialogs):由应用触发、由 Google 引导的流程,帮助正当用户修好底层问题,比如从 Play 安装官方版本、或恢复网络连接,而不是把他们留在死胡同里。如果你需要在处置之后继续识别同一批硬件的重复滥用,device recall(设备召回,beta)就是为此设计的。
八、灰度上线,不打断真实用户
先遥测,后处置。在真正开始拦截之前,先记录几周判定结果及其分布,你才知道自己安装量里 UNEVALUATED 和 UNLICENSED 的真实比例,而不是别人博客里的数字。然后分级推进:先用默认的设备标签,其余标签稍后,而且一次只动一个页面。让客户端能区分「重试」与「不行」,并确保 API 完全不可用时应用能优雅降级——网络不稳的用户,不该因此失去他已付费的内容。
九、KappS 在这件事里的位置
KappS 帮开发者把账号与发布保持在经得起平台审视的状态——准备并提交合规材料,而不是绕过检查。具体包括:
- 发布卫生。用 Play App Signing 签名、走官方渠道上传、版本号与证书和 Google Play 认可的记录一致——这些正是
PLAY_RECOGNIZED可能出现的前提。 - 账号就绪。开发者验证、付款资料与税务记录都指向账号背后的同一个合法主体,避免上线被一个无关的审核环节卡住。
- 集成复核。帮你复核客户端如何申请 token、服务器依据什么分支、以及处置等级是否与业务风险相匹配。
我们不提供、也不会提供完整性绕过、判定伪造,或任何把篡改版对 Play 藏起来的工具。那恰恰是这个 API 存在的意义,既让账号承担风险,也不是我们提供的服务。唯一能长期通过完整性检查的办法,就是它本身是真的。
资料来源(Google 官方文档):Play Integrity API —— Overview;Setup;Make a standard request;Make a classic request;About integrity verdicts;Use device recall;Handle error codes;Remediation dialogs(developer.android.com)。