1. 简介
上次更新时间:2023 年 1 月 4 日
什么是 Play Integrity?
Play Integrity API 有助于保护您的应用和游戏,使其免受可能存在风险的欺诈性互动的危害。您可以使用 Play Integrity API 获取与您的应用和设备相关的完整性判定,从而帮助您通过适当的措施来减少攻击和滥用行为,例如欺诈、欺骗和未经授权的访问。
旧版解决方案
Play Integrity API 取代了两个旧版解决方案:应用许可库和 SafetyNet Attestation API。建议为新应用使用 Play Integrity。对于使用旧版解决方案的现有应用,开发者应考虑改为使用 Play Integrity。
选择路径
Play Integrity 包含适用于不同应用类型的库选项:
- 使用 C/C++ 编写的游戏或其他应用
- 使用 Kotlin 或 Java 编程语言编写的应用
- 使用 Unity 引擎开发的游戏
此 Codelab 包含所有这三个选项的路径。您可以选择与您的开发需求相关的路径。此 Codelab 涉及构建和部署后端服务器。此服务器由所有三种应用类型共用。
构建内容
在此 Codelab 中,您会将 Play Integrity 集成到一个示例应用中,并使用该 API 检查设备和应用完整性。您将部署一个小型后端服务器应用,该应用将用于支持完整性检查流程。
学习内容
- 如何将 Play Integrity 库集成到应用中
- 如何使用 Play Integrity API 执行完整性检查
- 如何使用服务器安全地处理完整性判定
- 如何解读完整性判定结果
此 Codelab 主要介绍 Play Integrity。我们不会详细介绍不相关的概念和代码块,但是会提供相应代码块供您复制和粘贴。
所需条件
- 拥有一个已有效注册为 Android 开发者,并且有权访问 Play 管理中心 和 Google Cloud 控制台的 Google 账号。
- 对于 C++ 或 Kotlin 路径,使用 Android Studio 2021.1.1 或更高版本。
- 对于 Unity 路径,使用 Unity 2020 LTS 或更高版本。
- 一部已连接计算机并且已启用开发者选项和 USB 调试的 Android 设备。
此 Codelab 包含有关为 build 签名以及将 build 上传到 Play 管理中心的资源链接,但用户应当对此过程有一定的了解。
2. 获取代码
可以在 Git 代码库中获取相应的 Codelab 项目。代码库的父级目录中包含以下四个目录:
server
目录包含要部署的示例服务器的代码。cpp
目录包含一个 Android Studio 项目,用于将 Play Integrity 添加到 C++ 游戏或应用。kotlin
目录包含一个 Android Studio 项目,用于将 Play Integrity 添加到标准 Android 应用。unity
目录包含一个使用 2020 LTS 版本的 Unity 引擎创建的项目,用于将 Play Integrity 添加到 Unity 项目。
每个客户端示例目录中都包含以下两个子目录:
start
目录,其中包含供我们在此 Codelab 中进行修改的初始项目版本。final
目录,在此 Codelab 完成时,项目应与此目录中的项目版本相同。
克隆代码库
在命令行中,切换到您希望包含根 add-play-integrity-codelab
目录的目录,然后使用以下语法从 GitHub 克隆项目:
git clone https://github.com/android/add-play-integrity-codelab.git
添加依赖项(仅限 C++)
如果您计划使用 C++ 路径,则需要初始化代码库子模块以设置用于界面的 Dear ImGui 库。为此,请在命令行中运行以下命令:
- 将工作目录更改为:
add-play-integrity-codelab/cpp/start/third-party
git submodule update --init
3. 了解客户端和服务器功能
Play Integrity 安全模型的一个关键要素是将验证操作从设备转移到由您控制的安全服务器。在服务器上执行验证操作可防范各种威胁情形,例如遭破解的设备试图部署重放攻击或篡改消息内容。
此 Codelab 示例服务器仅作示例之用,为简单起见,并未包含许多在生产环境中需要的功能。所生成的值列表仅存储在内存中,而未基于永久性存储。服务器端点也未配置为需要身份验证。
performCommand 端点
服务器提供通过 HTTP POST
请求访问的 /performCommand
端点。请求正文应为具有以下键值对的 JSON 载荷:
{
"commandString": "command-here",
"tokenString": "token-here"
}
/performCommand
端点返回具有以下键值对的 JSON 载荷:
{
"commandSuccess": true/false,
"diagnosticMessage": "summary text",
"expressToken": "token-here"
}
commandString
参数的实际内容无关紧要。服务器会在使用 Play Integrity 时验证 commandString
的完整性,但不会使用命令值。所有客户端版本均使用以下值:"TRANSFER FROM alice TO bob CURRENCY gems QUANTITY 1000"
。
tokenString
参数的值必须为:
- 由 Play Integrity API 生成的令牌
- 上一次成功调用
/performCommand
后返回的快捷令牌
服务器解密并验证 Play Integrity API 提供的令牌。解密结果是完整性信号的 JSON 载荷。服务器可以选择是否同意该命令,具体取决于信号的值。如果系统成功解密令牌,diagnosticMessage
中会返回信号的摘要说明。您通常不会在正式版应用中将此信息返回给客户端,但 Codelab 客户端会使用此信息来显示操作结果,这就不必查看服务器日志。如果在处理令牌时发生错误,系统会在 diagnosticMessage
中返回错误。
Nonce 生成
若要发出 Play 完整性请求,则需要生成 Nonce 并将其与请求相关联。Nonce 有助于确保完整性请求是唯一的,并且仅处理一次。此外,Nonce 还可用于验证与完整性请求相关联的消息内容是否未被篡改。如需详细了解 Play Integrity Nonce,请参阅此文档。
在此 Codelab 中,系统会通过组合以下两个值来生成 Nonce:
- 128 位随机数字,由加密安全的随机数生成器生成
commandString
值的 SHA-256 哈希值
Play Integrity API 要求 Nonce 是一个经过网址编码且未填充的 Base64 字符串。为了创建 Nonce 字符串,此 Codelab 会将随机数字和哈希值的字节数组转换为十六进制字符串并进行串联。生成的字符串是一个有效的 Base64 字符串,但未采用 Base64 进行编码或解码。
此 Codelab 中的客户端会使用 HTTP GET
请求在服务器上调用 /getRandom
端点,以检索随机数字。这是最安全的随机生成方法,因为服务器可以验证其是否为命令请求中使用的随机数字的来源。不过,这需要额外的服务器往返。客户端可以通过自行生成随机号码来消除这种往返,但需要权衡安全性。
极速令牌
由于调用 PIA 的开销很高,因此服务器还提供了极速令牌,这是身份验证的替代方法,能够以较低的安全性为代价减少开销。通过完整性检查的成功 /serverCommand
调用会在 expressToken
字段中返回极速令牌。调用 Play Integrity API 需要很高的计算开销,因此这旨在保护高价值的操作。通过返回极速令牌,服务器可执行安全性较低的身份验证,以执行可能不太重要或者经常使用 Play Integrity API 进行验证的完整操作。调用 /serverCommand
时,可以使用极速令牌,而不必使用 Play Integrity 令牌。每个极速令牌都是一次性的。使用有效的极速令牌成功调用 /serverCommand
会返回新的极速令牌。
在此 Codelab 实现中,由于在服务器上生成的极速令牌具有唯一性,因此命令仍会受到针对重放攻击的保护。但是,极速令牌的安全性较低,因为它们会忽略针对命令修改的哈希保护,并且它们无法检测在首次调用 Play Integrity API 后发生的设备修改。
此 Codelab 的服务器架构
对于此 Codelab,您可以借助在 Kotlin 中使用 Ktor 框架编写的随附示例服务器程序来实例化服务器。该项目包含用于将其部署到 Google Cloud App Engine 的配置文件。此 Codelab 中的说明介绍了如何构建应用并将其部署到 App Engine。如果您使用其他云服务商,Ktor 提供了关于多项云服务的部署说明。您可以相应地修改项目并部署到您的首选服务。您也可以选择部署到自己的本地服务器实例。
无需为服务器使用示例代码。如果您有首选的 Web 应用框架,则可以使用 Codelab 示例服务器作为指导,在自己的框架中实现 /getRandom
和 /performCommand
端点。
客户端设计
所有三种客户端版本(C++、Kotlin 和 Unity 引擎)均具有类似的界面。
Request Random 按钮将调用服务器上的 /getRandom
端点。结果会显示在文本字段中。此按钮可用于在添加 Play Integrity 集成之前验证服务器连接和功能。
在此 Codelab 的开始部分,Call server with integrity check 按钮不会执行任何操作。您将按照以下步骤为以下操作添加代码:
- 调用
/getRandom
以获取随机数字 - 生成 Nonce
- 使用 Nonce 创建 Play 完整性请求
- 使用 Play Integrity 请求生成的令牌调用
/performCommand
- 显示
/performCommand
的结果
命令的结果会显示在文本字段中。对于成功的命令,结果将包含 Play 完整性检查返回的设备判定信息摘要。
成功执行 Call server with integrity check 操作后,应用会显示 Call server with express token 按钮。它使用上一个 /performCommand
中的极速令牌调用 /performCommand
。文本字段用于显示命令成功或失败。返回的极速令牌的值会显示在用于随机数字的文本字段中。
Play Integrity API 会返回错误代码以报告错误。如需详细了解这些错误代码,请参阅 Play Integrity 文档。有些错误可能是由环境条件(例如互联网连接不稳定或设备过载)导致的。对于此类错误,请考虑使用指数退避算法添加重试选项。在此 Codelab 中,Play Integrity 错误会输出到 Logcat 中。
4. 设置 Google Cloud App Engine
如需使用 Google Cloud,请执行以下步骤:
- 在 Google Cloud Platform 中注册。请使用您注册 Play 管理中心时所用的 Google 账号。
- 创建结算账号。
- 安装并初始化 Google Cloud SDK。
使用新安装的 Google Cloud CLI 运行以下命令:
- 安装 Java 版 App Engine 扩展程序:
gcloud components install app-engine-java
- 创建一个新的 Cloud 项目,将以下命令中的
$yourname
替换为一些唯一标识符:gcloud projects create $yourprojectname --set-as-default
- 在 Cloud 项目中创建一个 App Engine 应用:
gcloud app create
5. 部署服务器
设置应用软件包名称
在此步骤中,您需要将应用软件包名称添加到服务器代码中。在文本编辑器中,打开 ValidateCommand.kt
源文件。该文件位于以下目录中:
add-play-integrity-codelab/server/src/main/kotlin/com/google/play/integrity/codelab/server/util
找到以下行,并将占位符文本替换为唯一的软件包标识符,然后保存文件:
const val APPLICATION_PACKAGE_IDENTIFIER = "com.your.app.package"
之后,您需要在客户端项目中设置此标识符,然后再将应用上传到 Play 管理中心。
构建服务器并部署到 App Engine
使用 Google Cloud CLI 从 add-play-integrity/server
目录运行以下命令来构建和部署服务器:
在 Linux 或 macOS 上:
./gradlew appengineDeploy
在 Microsoft Windows 上:
gradlew.bat appengineDeploy
记下成功部署的输出中的已部署服务位置。您需要使用此网址来配置客户端,以便与服务器进行通信。
验证部署
您可以使用 Google Cloud CLI 运行以下命令来验证服务器是否正常运行:
gcloud app browse
此命令将打开网络浏览器并打开根网址。从根网址访问时,示例服务器应显示 Hello World! 消息。
6. 在 Play 管理中心内配置应用
在 Play 管理中心内配置应用完整性
如果您的 Play 管理中心内已有应用条目,则可以在此 Codelab 中使用该应用条目。或者,您也可以在 Play 管理中心内按照相应步骤创建新应用。在 Play 管理中心内选择或创建应用后,您需要配置应用完整性。在 Play 管理中心的左侧菜单中的发布部分中,转到应用完整性。
创建并关联 Google Cloud 项目
点击关联云项目按钮。选择您用于服务器的 Google Cloud 项目,然后点击关联项目按钮。
针对您服务器的 Google Cloud 访问权限
您的后端服务器必须对 Play Integrity API 在客户端上生成的完整性令牌进行解密。Play Integrity 提供两种密钥管理选项,分别是由 Google 生成和管理的密钥,以及开发者提供的密钥。此 Codelab 使用的是建议的默认选项,即由 Google 管理的密钥。
借助 Google 管理的密钥,您的后端服务器会将加密的完整性令牌传递给 Google Play 服务器进行解密。此 Codelab 中的服务器使用 Google API 客户端库与 Google Play 服务器进行通信。
现在,服务器已经启动并正常运行,并且您已经在 Play 管理中心内配置了应用,接下来可以开始自定义与所选平台相对应的一个或多个客户端了。特定平台的所有步骤会汇总在一起,因此对于不使用的平台,您可以跳过相关说明。
7. 构建并运行客户端 (C++)
运行 Android Studio。在 Welcome to Android Studio 窗口中,点击 Open 按钮并打开位于 add-play-integrity-codelab/cpp/start
的 Android Studio 项目。
更新应用 ID
在将 build 上传到 Google Play 之前,您需要将应用 ID 从默认值更改为其他唯一值。请执行以下步骤:
- 在 Android Studio 的 Project 窗格中,于
start/app
下找到build.gradle
文件并打开它。 - 找到
applicationId
语句。 - 将
com.google.play.integrity.codelab.cpp
更改为您在部署服务器时选择的软件包名称,然后保存该文件。 - 文件顶部会显示一个横幅,告知您 Gradle 文件已更改。点击 Sync Now 即可重新加载并重新同步文件。
- 在 Android Studio 的 Project 窗格中,打开
start/app/src/main
下的AndroidManifest.xml
文件。 - 找到
package="com.example.google.codelab.playintegritycpp"
语句。 - 将
com.example.google.codelab.playintegritycpp
替换为您的唯一的软件包名称,然后保存该文件。 - 在 Android Studio 的 Project 窗格中,打开
start/app/src/main/java/com.example.google.codelab.playintegritycpp
下的PlayIntegrityCodelabActivity
文件。 - 找到
package com.example.google.codelab.playintegritycpp
语句。 - 将
com.example.google.codelab.playintegritycpp
替换为您的唯一的软件包名称。 - 右键点击新的软件包名称,然后选择 Show Context Actions。
- 选择 Move to(新的软件包名称)。
- 如果文件顶部显示了 Sync Now 按钮,请选择该按钮。
更新服务器网址
需要对该项目进行更新,将其指向您部署服务器的网址位置。
- 在 Android Studio 的 Project 窗格中,打开
start/app/src/main/cpp
下的server_urls.hpp
文件。 - 将部署服务器时显示的根网址添加到
GET_RANDOM_URL
和PERFORM_COMMAND_URL
定义中,然后保存该文件。
结果应如下所示:
constexpr char GET_RANDOM_URL[] = "https://your-play-integrity-server.uc.r.appspot.com/getRandom";
constexpr char PERFORM_COMMAND_URL[] = "https://your-play-integrity-server.uc.r.appspot.com/performCommand";
具体网址取决于您的项目名称,以及用于部署服务器的 Google Cloud 区域。
构建并运行
连接针对开发进行了配置的 Android 设备。在 Android Studio 中,构建项目并在已连接的设备上运行。应用应如下所示:
触摸 Request Random 按钮,这将执行用于向服务器发出 HTTP 请求以请求随机数的代码。短暂延迟后,您应该会在界面上看到随机数字:
如果系统显示错误消息,Logcat 窗格输出中可能会包含更多详细信息。
成功检索到随机值即表明您与服务器通信正常,接下来就可以开始集成 Play Integrity API 了。
8. 将 Play Integrity 添加到项目 (C++)
下载 SDK
您需要下载并解压缩 Play Core SDK。请执行以下步骤:
- 从 Play Core 原生 SDK 页面下载打包为 .zip 文件的 Play Core SDK。
- 解压该 ZIP 文件。
- 确保新解压的目录名为
play-core-native-sdk
,并将其复制或移动到add-play-integrity-codelab/cpp/start
目录中。
更新 build.gradle
在 Android Studio 的 Project 窗格中,打开 start/app
目录中的模块级 build.gradle
文件。
在 apply plugin: 'com.android.application'
行下方添加以下行:
def playcoreDir = file("../play-core-native-sdk")
在 defaultConfig
代码块中找到 externalNativeBuild
代码块,并更改 cmake
代码块内的 arguments
语句,确保与以下内容相一致:
arguments "-DANDROID_STL=c++_shared",
"-DPLAYCORE_LOCATION=$playcoreDir"
在 android
代码块的末尾添加以下代码:
buildTypes {
release {
proguardFiles getDefaultProguardFile("proguard-android.txt"),
"proguard-rules.pro",
"$playcoreDir/proguard/common.pgcfg",
"$playcoreDir/proguard/integrity.pgcfg"
}
}
在 dependencies
代码块的末尾添加下面这行代码:
implementation files("$playcoreDir/playcore.aar")
保存您所做的更改。文件顶部会显示一个横幅,告知您 Gradle 文件已更改。点击 Sync Now 即可重新加载并重新同步文件。
更新 CMakeList.txt
在 Android Studio 的 Project 窗格中,打开 start/app/src/main/cpp
目录中的 CMakeLists.txt
文件。
在 find_package
命令下方添加以下几行代码:
include("${PLAYCORE_LOCATION}/playcore.cmake")
add_playcore_static_library()
找到 target_include_directories(game PRIVATE
代码行并在其下方添加以下代码行:
${PLAYCORE_LOCATION}/include
找到 target_link_libraries(game
代码行并在其下方添加以下代码行:
playcore
保存文件。文件顶部会显示一个横幅,告知您外部 build 文件已更改。点击 Sync Now 即可重新加载并重新同步文件。
从 Build 菜单中选择 Make Project,然后验证项目是否构建成功。
9. 发出完整性请求 (C++)
应用会使用 Play Integrity API 请求令牌,以获取完整性信息,然后系统会将令牌发送到您的服务器以进行解密和验证。现在,您将向项目中添加代码以初始化 Play Integrity API,并使用该 API 来发出完整性请求。
添加命令按钮
在实际应用或游戏中,您可以在特定活动(例如在商店中购买或加入多人游戏会话)之前执行完整性检查。在此 Codelab 中,我们将向界面中添加一个按钮,可用于手动触发完整性检查并调用服务器,以便传递生成的 Play Integrity 令牌。
此 Codelab 项目包含一个 ClientManager
类,该类在 client_manager.cpp
和 client_manager.hpp
源文件中定义。为方便起见,此文件已添加到项目中,但缺少您现在要添加的实现代码。
如需添加界面按钮,请先从 Android Studio 的 Project 窗格的 start/app/src/main/cpp
目录中打开 demo_scene.cpp
文件。首先,找到空的 DemoScene::GenerateCommandIntegrity()
函数,并添加以下代码:
const auto commandResult =
NativeEngine::GetInstance()->GetClientManager()->GetOperationResult();
if (commandResult != ClientManager::SERVER_OPERATION_PENDING) {
if (ImGui::Button("Call server with integrity check")) {
DoCommandIntegrity();
}
}
接下来,找到空的 DemoScene::DoCommandIntegrity()
函数。添加以下代码。
ClientManager *clientManager = NativeEngine::GetInstance()->GetClientManager();
clientManager->StartCommandIntegrity();
mServerRandom = clientManager->GetCurrentRandomString();
保存文件。您现在可以更新示例的 ClientManager
类,以添加实际的 Play Integrity 功能。
更新 manager 头文件
在 Android Studio 的 Project 窗格的 start/app/src/main/cpp
目录中,打开 client_manager.hpp
文件。
在 #include "util.hpp"
代码行下方添加以下代码,以便添加 Play Integrity API 的头文件:
#include "play/integrity.h"
ClientManager
类需要存储对 IntegrityTokenRequest
和 IntegrityTokenResponse
对象的引用。将以下代码行添加到 ClientManager
类定义的底部:
IntegrityTokenRequest *mTokenRequest;
IntegrityTokenResponse *mTokenResponse;
保存文件。
初始化和关闭 Play Integrity
在 Android Studio 的 Project 窗格中,打开 start/app/src/main/cpp
目录中的 client_manager.cpp
文件。
找到 ClientManager::ClientManager()
构造函数。请将 mInitialized = false;
语句替换为以下代码:
mTokenRequest = nullptr;
mTokenResponse = nullptr;
const android_app *app = NativeEngine::GetInstance()->GetAndroidApp();
const IntegrityErrorCode errorCode = IntegrityManager_init(app->activity->vm,
app->activity->javaGameActivity);
if (errorCode == INTEGRITY_NO_ERROR) {
mInitialized = true;
} else {
mInitialized = false;
ALOGE("Play Integrity initialization failed with error: %d", errorCode);
ALOGE("Fatal Error: Play Integrity is unavailable and cannot be used.");
}
将以下代码添加到 ClientManager::~ClientManager()
析构函数:
if (mInitialized) {
IntegrityManager_destroy();
mInitialized = false;
}
请求完整性令牌
从 Play Integrity API 请求完整性令牌是一项异步操作。您需要创建一个令牌请求对象,为其分配一个 Nonce 值,然后发出令牌请求。为此,请将以下代码添加到空的 ClientManager::StartCommandIntegrity()
函数:
// Only one request can be in-flight at a time
if (mStatus != CLIENT_MANAGER_REQUEST_TOKEN) {
mResult = SERVER_OPERATION_PENDING;
// Request a fresh random
RequestRandom();
if (mValidRandom) {
GenerateNonce();
IntegrityTokenRequest_create(&mTokenRequest);
IntegrityTokenRequest_setNonce(mTokenRequest, mCurrentNonce.c_str());
const IntegrityErrorCode errorCode =
IntegrityManager_requestIntegrityToken(mTokenRequest, &mTokenResponse);
if (errorCode != INTEGRITY_NO_ERROR) {
// Log the error, in a real application, for potentially
// transient errors such as network connectivity, you should
// add retry with an exponential backoff
ALOGE("Play Integrity returned error: %d", errorCode);
CleanupRequest();
mStatus = CLIENT_MANAGER_IDLE;
} else {
mStatus = CLIENT_MANAGER_REQUEST_TOKEN;
}
}
}
由于令牌请求是异步运行的,因此您需要检查其完成情况。ClientManager
类有一个 Update()
函数,系统会在应用更新循环中调用该函数。将以下代码添加到 ClientManager::Update()
函数以检查令牌请求的状态,并在完成后处理结果:
if (mStatus == CLIENT_MANAGER_REQUEST_TOKEN) {
IntegrityResponseStatus responseStatus = INTEGRITY_RESPONSE_UNKNOWN;
const IntegrityErrorCode errorCode =
IntegrityTokenResponse_getStatus(mTokenResponse, &responseStatus);
if (errorCode != INTEGRITY_NO_ERROR) {
// Log the error, in a real application, for potentially
// transient errors such as network connectivity, you should
// add retry with an exponential backoff
ALOGE("Play Integrity returned error: %d", errorCode);
CleanupRequest();
mStatus = CLIENT_MANAGER_IDLE;
} else if (responseStatus == INTEGRITY_RESPONSE_COMPLETED) {
std::string tokenString = IntegrityTokenResponse_getToken(mTokenResponse);
SendCommandToServer(tokenString);
CleanupRequest();
mStatus = CLIENT_MANAGER_RESPONSE_AVAILABLE;
}
}
清理请求对象
完成令牌请求和响应对象后,您需要告知 Play Integrity API,以便它可以销毁这些令牌和响应对象,并收回其资源。将以下代码添加到 ClientManager::CleanupRequest()
函数中:
if (mTokenResponse != nullptr) {
IntegrityTokenResponse_destroy(mTokenResponse);
mTokenResponse = nullptr;
}
if (mTokenRequest != nullptr) {
IntegrityTokenRequest_destroy(mTokenRequest);
mTokenRequest = nullptr;
}
从 Build 菜单中选择 Make Project,并验证项目是否构建成功。
10. 将令牌发送到服务器 (C++)
现在,您需要添加代码,以便向服务器发送包含完整性令牌的命令。您还需要添加代码以处理结果。
将以下代码添加到 ClientManager::SendCommandToServer()
函数中:
// Note that for simplicity, we are doing HTTP operations as
// synchronous blocking instead of managing them from a
// separate network thread
HTTPClient client;
std::string errorString;
// Manually construct the json payload for ServerCommand
std::string payloadString = COMMAND_JSON_PREFIX;
payloadString += TEST_COMMAND;
payloadString += COMMAND_JSON_TOKEN;
payloadString += token;
payloadString += COMMAND_JSON_SUFFIX;
auto result = client.Post(PERFORM_COMMAND_URL, payloadString, &errorString);
if (!result) {
ALOGE("SendCommandToServer Curl reported error: %s", errorString.c_str());
mResult = SERVER_OPERATION_NETWORK_ERROR;
} else {
ALOGI("SendCommandToServer result: %s", (*result).c_str())
// Preset to success, ParseResult will set a failure result if the parsing
// errors.
mResult = SERVER_OPERATION_SUCCESS;
ParseResult(*result);
}
将以下代码添加到 ClientManager::ParseResult()
函数中:
bool validJson = false;
JsonLookup jsonLookup;
if (jsonLookup.ParseJson(resultJson)) {
// Look for all of our needed fields in the returned json
auto commandSuccess = jsonLookup.GetBoolValueForKey(COMMANDSUCCESS_KEY);
if (commandSuccess) {
auto diagnosticString = jsonLookup.GetStringValueForKey(DIAGNOSTICMESSAGE_KEY);
if (diagnosticString) {
auto expressString = jsonLookup.GetStringValueForKey(EXPRESSTOKEN_KEY);
if (expressString) {
if (*commandSuccess) {
// Express token only valid if the server reports the command succeeded
mValidExpressToken = true;
} else {
mValidExpressToken = false;
mResult = SERVER_OPERATION_REJECTED_VERDICT;
}
mCurrentSummary = *diagnosticString;
mCurrentExpressToken = *expressString;
validJson = true;
}
}
}
}
if (!validJson) {
mResult = SERVER_OPERATION_INVALID_RESULT;
}
现在,您将生成已签名的 app bundle,并将其上传到 Play 管理中心以测试应用。
11. 构建和上传 (C++)
为应用创建和设置密钥库
Android 系统要求所有应用必须先使用证书进行数字签名,然后才能安装到设备上或进行更新。
在此 Codelab 中,我们将为应用创建一个密钥库。如果您要发布现有游戏的更新,请重用您发布旧版应用时所使用的密钥库。
创建密钥库并构建发布 app bundle
按照使用 Android Studio 创建密钥库中的步骤创建一个密钥库,然后使用它来生成游戏的已签名发布 build。在 Android Studio 中,从 Build 菜单中选择 Generate Signed Bundle / APK 以开始构建流程。当系统提示您选择 Android App Bundle 或 APK 时,请选择 App Bundle 选项。完成此过程后,您会得到一个适合上传到 Google Play 管理中心的 .aab 文件。
上传到 Play 管理中心
创建 app bundle 文件后,将其上传到 Play 管理中心。建议使用内部测试轨道来快速访问您的 build。
运行测试 build
您现在应从 Play 商店下载并运行测试 build。目前,函数中有一个占位符成功代码,会将完整性令牌发送到您的服务器,因此启动完整性检查应会成功,并显示以下内容:
恭喜,您已将 Play Integrity 集成到 C++ 应用中!请继续查看任何其他客户端示例,或跳到此 Codelab 的最后部分。
12. 构建和运行客户端 (Unity)
此 Codelab Unity 项目是使用 Unity 2020 LTS (2020.3.31f1) 创建的,但应与更高版本的 Unity 兼容。适用于 Unity 的 Play Integrity 插件与 Unity 2018 LTS 及更高版本兼容。
项目设置
请执行以下步骤:
- 在 Unity Hub 或 Unity 编辑器中,打开位于
add-play-integrity-codelab/unity/start
中的 Unity 项目。 - 项目加载后,从 Unity 的 File 菜单中选择 Build Settings...。
- 在 Build Settings 窗口中,将平台更改为 Android。
- 在 Build Settings 窗口中,点击 Player Settings... 按钮。
- 在 Project Settings 窗口中,选择 Player 类别,然后找到 Android for Settings 部分。展开 Other Settings 列表。
- 在 Identification 下找到 Package Name 条目。
- 将软件包名称更改为您在部署服务器时选择的标识符。
- 关闭 Project Settings 窗口。
- 从 Unity 的 File 菜单中,选择 Save Project。
更新服务器网址
需要对该项目进行更新,将其指向您部署服务器的网址位置。为此,请执行以下步骤:
- 在 IDE 或文本编辑器中,打开
Scripts
文件夹中的PlayIntegrityController.cs
文件。 - 将
URL_GETRANDOM
和URL_PERFORMCOMMAND
变量的值更改为指向您的服务器。 - 保存文件。
结果应如下所示:
private readonly string URL_GETRANDOM = "https://your-play-integrity-server.uc.r.appspot.com/getRandom";
private readonly string URL_PERFORMCOMMAND = "https://your-play-integrity-server.uc.r.appspot.com/performCommand";
具体网址取决于您的项目名称,以及用于部署服务器的 Google Cloud 区域。
测试服务器功能
您可以通过在 Unity 编辑器中运行项目来测试服务器功能。请执行以下步骤:
- 打开位于
Scenes
文件夹中的SampleScene
场景文件。 - 点击编辑器中的 Play 按钮。
- 点击 Game 屏幕中的 Request Random 按钮。
短暂延迟后,屏幕上应显示随机值,类似于以下内容:
13. 将 Play Integrity 插件添加到项目 (Unity)
下载插件
在网络浏览器中,打开 Play Unity 插件 GitHub 代码库的版本页面。使用最新版本的插件。下载 com.google.play.integrity-<version>
。在 Assets 列表中下载适用于 Play Integrity 的 unitypackage
文件。
安装插件
在 Unity 编辑器菜单栏中,依次选择 Assets -> Import Package -> Custom Package...,然后打开您下载的 .unitypackage
文件。出现 Import Unity Package 窗口后,点击 Import 按钮。
该插件包含适用于 Unity (EDM4U) 的 External Dependency Manager。EDM4U 为使用 Play Integrity 所需的 Java 组件实现了自动依赖项解析。当系统提示您启用自动依赖项解析时,请点击 Enable 按钮。
强烈建议您使用自动解析功能。依赖项问题可能会导致您的项目无法构建或运行。
14. 发出完整性请求 (Unity)
创建完整性请求
如需创建完整性请求,请执行以下步骤。
- 在 IDE 或文本编辑器中,打开
Scripts
文件夹中的PlayIntegrityController.cs
文件。 - 将以下代码行添加到文件顶部的
using
语句块中:
using Google.Play.Integrity;
- 找到
RunIntegrityCommand()
函数并将yield return null;
语句替换为以下代码:
// Call our server to retrieve a random number.
yield return GetRandomRequest();
if (!string.IsNullOrEmpty(_randomString))
{
// Create an instance of an integrity manager.
var integrityManager = new IntegrityManager();
// Request the integrity token by providing a nonce.
var tokenRequest = new IntegrityTokenRequest(GenerateNonceString(_randomString,
TEST_COMMAND));
var requestIntegrityTokenOperation =
integrityManager.RequestIntegrityToken(tokenRequest);
// Wait for PlayAsyncOperation to complete.
yield return requestIntegrityTokenOperation;
// Check the resulting error code.
if (requestIntegrityTokenOperation.Error != IntegrityErrorCode.NoError)
{
// Log the error, in a real application, for potentially
// transient errors such as network connectivity, you should
// add retry with an exponential backoff
Debug.Log($@"IntegrityAsyncOperation failed with error:
{requestIntegrityTokenOperation.Error.ToString()}");
yield break;
}
// Get the response.
var tokenResponse = requestIntegrityTokenOperation.GetResult();
// Send the command to our server with a POST request, including the
// token, which will be decrypted and verified on the server.
yield return PostServerCommand(tokenResponse.Token);
}
将命令发送到服务器
继续修改 PlayIntegrityController.cs
文件,找到 PostServerCommand()
函数并将 yield return null;
语句并替换为以下代码:
// Start a HTTP POST request to the performCommand URL, sending it the
// command and integrity token data provided by Play Integrity.
var serverCommand = new ServerCommand(TEST_COMMAND, tokenResponse);
var commandRequest = new UnityWebRequest(URL_PERFORMCOMMAND, "POST");
string commandJson = JsonUtility.ToJson(serverCommand);
byte[] jsonBuffer = Encoding.UTF8.GetBytes(commandJson);
commandRequest.uploadHandler = new UploadHandlerRaw(jsonBuffer);
commandRequest.downloadHandler = new DownloadHandlerBuffer();
commandRequest.SetRequestHeader(CONTENT_TYPE, JSON_CONTENT);
yield return commandRequest.SendWebRequest();
if (commandRequest.result == UnityWebRequest.Result.Success)
{
// Parse the command result Json
var commandResult = JsonUtility.FromJson<CommandResult>(
commandRequest.downloadHandler.text);
if (commandResult != null)
{
resultLabel.text = commandResult.diagnosticMessage;
_expressToken = commandResult.expressToken;
if (commandResult.commandSuccess)
{
resultLabel.color = Color.green;
expressButton.SetActive(true);
}
else
{
resultLabel.color = Color.black;
expressButton.SetActive(false);
}
}
else
{
Debug.Log("Invalid CommandResult json");
}
}
else
{
Debug.Log($"Web request error on processToken: {commandRequest.error}");
}
保存文件。
15. 构建和上传 (Unity)
使用 Unity 编辑器 Android 密钥库管理器为 build 配置签名信息,以便上传到 Play 管理中心。
配置签名信息后,请执行以下步骤:
- 从 Unity 的 File 菜单中,依次选择 Build -> Build Settings...。
- 确保 Scenes in Build 列表中包含
SampleScene
。 - 请务必选中 Build App Bundle (Google Play) 复选框。
- 点击 Build 按钮并为导出文件命名。
创建 app bundle 文件后,将其上传到 Play 管理中心。建议使用内部测试轨道来快速访问您的 build。
现在,您可以下载并安装 build 以运行完整性检查。结果应如下所示:
恭喜,您已将 Play Integrity 集成到 Unity 引擎项目中!请继续查看任何其他客户端示例,或跳到此 Codelab 的最后部分。
16. 构建并运行项目 (Kotlin)
运行 Android Studio。在 Welcome to Android Studio 窗口中,点击 Open 按钮并打开位于 add-play-integrity-codelab/kotlin/start
的 Android Studio 项目。
更新应用 ID
在将 build 上传到 Google Play 之前,您需要将应用 ID 从默认值更改为其他唯一值。请执行以下步骤:
- 在 Android Studio 的 Project 窗格中,打开模块
PlayIntegrityCodelab.app
的build.gradle
文件。 - 找到
applicationId
语句。 - 将
com.example.google.codelab.playintegritykotlin
更改为您在部署服务器时选择的标识符并保存文件。 - 文件顶部会显示一个横幅,告知您 Gradle 文件已更改。点击 Sync Now 即可重新加载并重新同步文件。
更新服务器网址
需要对该项目进行更新,将其指向您部署服务器的网址位置。为此,请执行以下步骤:
- 在 Android Studio 的 Project 窗格中,打开
start/app/src/main/java/com.example.google.codelab.playintegritykotlin/integrity
下的IntegrityServer
文件。 - 将网址从
‘https://your.play-integrity.server.com'
更改为您的服务器的基础网址,然后保存文件。
结果应如下所示:
private val SERVER_URL: String = "https://your-play-integrity-server.uc.r.appspot.com"
具体网址取决于您的项目名称,以及用于部署服务器的 Google Cloud 区域。
构建并运行
连接针对开发进行了配置的 Android 设备。在 Android Studio 中,构建项目并在已连接的设备上运行。应用应如下所示:
启动时,应用会调用您服务器上的 getRandom
端点并显示结果。如果出现错误,例如网址不正确或服务器无法正常运行,系统会显示错误对话框。您可以选择 Request Random 按钮,从服务器检索新的随机数字。目前,Call server with integrity check 按钮不会执行任何操作。您将在以下几个部分中添加该功能。
17. 将 Play Integrity 添加到项目 (Kotlin)
如需为项目添加 Play Integrity 库和支持依赖项,请按以下步骤操作:
- 在 Android Studio 的 Project 窗格中,打开
start/app
下的build.gradle
文件。 - 在文件底部找到
dependencies
代码块。 - 将以下代码行添加到
dependencies
代码块底部:
implementation "com.google.android.play:integrity:1.0.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.1"
- 保存文件。
- 文件顶部会显示一个横幅,告知您 Gradle 文件已更改。点击 Sync Now 即可重新加载并重新同步文件。
Kotlin 示例使用协程。kotlinx-coroutines-play-services
库添加了一些扩展程序,这些扩展程序有助于从 Kotlin 协程内使用 Play Integrity 异步 Task
对象。
18. 发出完整性请求 (Kotlin)
在 Android Studio 的 Project 窗格中,打开 start/app/src/main/java/com.example.google.codelab.playintegritykotlin/integrity
下的 IntegrityServer
文件。文件底部有一个空的 integrityCommand
函数。当用户按 Call server with integrity check 按钮时,界面会调用此函数。
您将向 integrityCommand
函数添加代码以执行以下操作:
- 从服务器检索要在构造 Nonce 以与完整性检查相关联时使用的新随机数字
- 调用 Play Integrity API 以发出完整性请求,并接收包含结果的完整性令牌
- 使用 HTTP
POST
请求向您的服务器发送命令和完整性令牌 - 处理和显示结果
将以下代码添加到空的 integrityCommand
函数:
// Set our state to working to trigger a switch to the waiting UI
_serverState.emit(ServerState(
ServerStatus.SERVER_STATUS_WORKING))
// Request a fresh random from the server as part
// of the nonce we will generate for the request
var integrityRandom = IntegrityRandom("", 0U)
try {
val returnedRandom = httpClient.get<IntegrityRandom>(
SERVER_URL + "/getRandom")
integrityRandom = returnedRandom
} catch (t: Throwable) {
Log.d(TAG, "getRandom exception " + t.message)
_serverState.emit(ServerState(ServerStatus.SERVER_STATUS_UNREACHABLE,
IntegrityRandom("", 0U)))
}
// If we have a random, we are ready to request an integrity token
if (!integrityRandom.random.isNullOrEmpty()) {
val nonceString = GenerateNonce.GenerateNonceString(TEST_COMMAND,
integrityRandom.random)
// Create an instance of an IntegrityManager
val integrityManager = IntegrityManagerFactory.create(context)
// Use the nonce to configure a request for an integrity token
try {
val integrityTokenResponse: Task<IntegrityTokenResponse> =
integrityManager.requestIntegrityToken(
IntegrityTokenRequest.builder()
.setNonce(nonceString)
.build()
)
// Wait for the integrity token to be generated
integrityTokenResponse.await()
if (integrityTokenResponse.isSuccessful && integrityTokenResponse.result != null) {
// Post the received token to our server
postCommand(integrityTokenResponse.result!!.token(), integrityRandom)
} else {
Log.d(TAG, "requestIntegrityToken failed: " +
integrityTokenResponse.result.toString())
_serverState.emit(ServerState(ServerStatus.SERVER_STATUS_FAILED_TO_GET_TOKEN))
}
} catch (t: Throwable) {
Log.d(TAG, "requestIntegrityToken exception " + t.message)
_serverState.emit(ServerState(ServerStatus.SERVER_STATUS_FAILED_TO_GET_TOKEN))
}
}
用于将命令 POST
到您的服务器的代码已拆分为一个单独的 postCommand
函数。将以下代码添加到空的 postCommand
函数:
try {
val commandResult = httpClient.post<CommandResult>(
SERVER_URL + "/performCommand") {
contentType(ContentType.Application.Json)
body = ServerCommand(TEST_COMMAND, tokenString)
}
_serverState.emit(ServerState(ServerStatus.SERVER_STATUS_REACHABLE,
integrityRandom,
commandResult.diagnosticMessage,
commandResult.commandSuccess,
commandResult.expressToken))
} catch (t: Throwable) {
Log.d(TAG, "performCommand exception " + t.message)
_serverState.emit(ServerState(ServerStatus.SERVER_STATUS_UNREACHABLE))
}
解决所有缺失的导入,然后保存文件。
19. 显示结果 (Kotlin)
使用判定摘要更新界面
界面目前会显示完整性判定摘要的占位符文本。如需将占位符替换为实际摘要,请执行以下步骤:
- 在 Android Studio 的 Project 窗格中,打开
start/app/src/main/java/com.example.google.codelab.playintegritykotlin/ui/main
下的MainView.kt
文件。 - 转到 MainUI 函数的末尾部分,找到
text = "None",
语句,并将其替换为以下代码:
text = state.serverState.serverVerdict,
- 解决所有缺失的导入,然后保存文件。
20. 构建和上传 (Kotlin)
为应用创建和设置密钥库
Android 系统要求所有应用必须先使用证书进行数字签名,然后才能安装到设备上或进行更新。
在此 Codelab 中,我们将为应用创建一个密钥库。如果您要发布现有游戏的更新,请重用您发布旧版应用时所使用的密钥库。
创建密钥库并构建发布 app bundle
按照使用 Android Studio 创建密钥库中的步骤创建一个密钥库,然后使用它来生成游戏的已签名发布 build。在 Android Studio 中,从 Build 菜单中选择 Generate Signed Bundle / APK 以开始构建流程。当系统提示您选择 Android App Bundle 或 APK 时,请选择 App Bundle 选项。完成此过程后,您会得到一个适合上传到 Google Play 管理中心的 .aab 文件。
上传到 Play 管理中心
创建 app bundle 文件后,将其上传到 Play 管理中心。建议使用内部测试轨道来快速访问您的 build。
运行测试 build
您现在应从 Play 商店下载并运行测试 build。选择 Call server with integrity check 按钮应会成功,并显示以下显示画面:
21. 恭喜
恭喜,您已成功将 Play Integrity 添加到 Android 应用中!