Play Integrity API menggunakan verdict integritas untuk menyampaikan informasi tentang validitas perangkat, aplikasi, dan pengguna. Server aplikasi Anda dapat menggunakan payload yang dihasilkan dalam verdict yang telah didekripsi dan diverifikasi guna menentukan cara terbaik untuk melanjutkan dengan tindakan atau permintaan tertentu di aplikasi Anda.
Membuat nonce
Saat melindungi tindakan di aplikasi dengan Play Integrity API, Anda dapat
memanfaatkan kolom nonce
untuk mengurangi jenis serangan tertentu, seperti
serangan modifikasi tidak sah person-in-the-middle (PITM) dan serangan replay. Play Integrity API
menampilkan nilai yang Anda tetapkan dalam kolom ini, di dalam respons integritas
yang ditandatangani.
Nilai yang ditetapkan di kolom nonce
harus diformat dengan benar:
String
- URL-safe
- Dienkode sebagai Base64 dan non-wrapping
- Minimum 16 karakter
- Maksimum 500 karakter
Berikut adalah beberapa cara umum untuk menggunakan kolom nonce
di Play
Integrity API. Untuk mendapatkan perlindungan terkuat dari nonce, Anda dapat menggabungkan
metode di bawah ini.
Melindungi tindakan bernilai tinggi terhadap modifikasi tidak sah
Anda dapat menggunakan kolom nonce
Play Integrity untuk melindungi konten
tindakan bernilai tinggi tertentu dari modifikasi tidak sah. Misalnya, game mungkin ingin
melaporkan skor pemain, dan Anda ingin memastikan skor ini tidak
dirusak oleh server proxy. Implementasinya adalah sebagai berikut:
- Pengguna memulai tindakan bernilai tinggi.
- Aplikasi Anda menyiapkan pesan yang ingin dilindungi, misalnya, dalam format JSON.
- Aplikasi Anda menghitung hash kriptografi dari pesan yang ingin dilindungi. Misalnya, dengan SHA-256, atau algoritme hashing SHA-3-256.
- Aplikasi Anda memanggil Play Integrity API, dan memanggil
setNonce()
untuk menetapkan kolomnonce
ke hash kriptografi yang dihitung di langkah sebelumnya. - Aplikasi Anda mengirim pesan yang ingin dilindungi, serta hasil bertanda tangan Play Integrity API ke server.
- Server aplikasi Anda memverifikasi bahwa hash kriptografi dari pesan yang
diterimanya cocok dengan nilai kolom
nonce
di hasil yang ditandatangani, dan menolak hasil yang tidak cocok.
Gambar 1 berisi diagram urutan yang menggambarkan langkah-langkah ini:
Gambar 1. Diagram urutan yang menunjukkan cara melindungi tindakan bernilai tinggi di aplikasi Anda terhadap modifikasi tidak sah.
Melindungi aplikasi Anda dari serangan replay
Untuk mencegah pengguna berbahaya menggunakan kembali respons sebelumnya dari
Play Integrity API, Anda dapat menggunakan kolom nonce
untuk mengidentifikasi setiap pesan
secara unik. Implementasinya adalah sebagai berikut:
- Anda memerlukan nilai unik secara global dengan cara yang tidak dapat diprediksi oleh pengguna yang berbahaya. Misalnya, angka acak yang diamankan secara kriptografis yang dihasilkan pada sisi server dapat menjadi nilai tersebut. Sebaiknya buat nilai 128 bit atau yang lebih besar.
- Aplikasi Anda memanggil Play Integrity API, dan memanggil
setNonce()
untuk menetapkan kolomnonce
ke nilai unik yang diterima oleh server aplikasi Anda. - Aplikasi Anda mengirimkan hasil bertanda tangan Play Integrity API ke server.
- Server Anda memverifikasi bahwa kolom
nonce
dalam hasil bertanda tangan cocok dengan nilai unik yang dibuat sebelumnya, dan menolak hasil apa pun yang tidak cocok.
Gambar 2 berisi diagram urutan yang menggambarkan langkah-langkah ini:
Gambar 2. Diagram urutan yang menunjukkan cara melindungi aplikasi dari serangan replay.
Menggabungkan kedua perlindungan
Kolom nonce
dapat digunakan untuk melindungi dari serangan replay
dan gangguan secara bersamaan. Untuk melakukannya, Anda dapat menambahkan
nilai unik secara global yang dihasilkan server ke hash pesan bernilai tinggi, dan menetapkan nilai ini
sebagai kolom nonce
di Play Integrity API. Implementasi yang menggabungkan
kedua pendekatan tersebut adalah sebagai berikut:
- Pengguna memulai tindakan bernilai tinggi.
- Aplikasi Anda meminta nilai unik kepada server untuk mengidentifikasi permintaan
- Server aplikasi Anda menghasilkan nilai unik secara global dengan cara yang tidak dapat diprediksi oleh pengguna berbahaya. Misalnya, Anda dapat menggunakan generator angka acak yang aman secara kriptografis untuk membuat nilai tersebut. Sebaiknya buat nilai 128 bit atau lebih besar.
- Server aplikasi Anda mengirimkan nilai unik secara global ke aplikasi.
- Aplikasi Anda menyiapkan pesan yang ingin dilindungi, misalnya, dalam format JSON.
- Aplikasi Anda menghitung hash kriptografi dari pesan yang ingin dilindungi. Misalnya, dengan SHA-256, atau algoritme hashing SHA-3-256.
- Aplikasi Anda membuat string dengan menambahkan nilai unik yang diterima dari server aplikasi, dan hash pesan yang ingin dilindungi.
- Aplikasi akan memanggil Play Integrity API, dan memanggil
setNonce()
untuk menetapkan kolomnonce
ke string yang dibuat pada langkah sebelumnya. - Aplikasi Anda mengirim pesan yang ingin dilindungi, serta hasil bertanda tangan Play Integrity API ke server.
- Server aplikasi Anda membagi nilai kolom
nonce
, dan memastikan bahwa hash kriptografi pesan, serta nilai unik yang sebelumnya dihasilkan cocok dengan nilai yang diharapkan, dan menolak hasil apa pun yang tidak cocok.
Gambar 3 berisi diagram urutan yang menggambarkan langkah-langkah ini:
Gambar 3. Diagram urutan yang menunjukkan cara melindungi aplikasi dari serangan replay dan melindungi tindakan bernilai tinggi di aplikasi Anda terhadap modifikasi tidak sah.
Meminta verdict integritas
Setelah membuat nonce, Anda dapat meminta verdict integritas dari Google Play. Caranya, selesaikan langkah-langkah berikut:
- Buat
IntegrityManager
seperti yang ditunjukkan dalam contoh berikut. - Buat
IntegrityTokenRequest
, yang menyediakan nonce melalui metodesetNonce()
di builder terkait. Aplikasi yang didistribusikan secara eksklusif di luar Google Play dan SDK juga harus menentukan nomor project Google Cloud melalui metodesetCloudProjectNumber()
. Aplikasi di Google Play ditautkan ke project Cloud di Konsol Play dan tidak perlu menetapkan nomor project Cloud dalam permintaan. Gunakan pengelola untuk memanggil
requestIntegrityToken()
yang menyediakanIntegrityTokenRequest
.
Kotlin
// Receive the nonce from the secure server. val nonce: String = ... // Create an instance of a manager. val integrityManager = IntegrityManagerFactory.create(applicationContext) // Request the integrity token by providing a nonce. val integrityTokenResponse: Task<IntegrityTokenResponse> = integrityManager.requestIntegrityToken( IntegrityTokenRequest.builder() .setNonce(nonce) .build())
Java
import com.google.android.gms.tasks.Task; ... // Receive the nonce from the secure server. String nonce = ... // Create an instance of a manager. IntegrityManager integrityManager = IntegrityManagerFactory.create(getApplicationContext()); // Request the integrity token by providing a nonce. Task<IntegrityTokenResponse> integrityTokenResponse = integrityManager .requestIntegrityToken( IntegrityTokenRequest.builder().setNonce(nonce).build());
Unity
IEnumerator RequestIntegrityTokenCoroutine() { // Receive the nonce from the secure server. var nonce = ... // Create an instance of a manager. var integrityManager = new IntegrityManager(); // Request the integrity token by providing a nonce. var tokenRequest = new IntegrityTokenRequest(nonce); var requestIntegrityTokenOperation = integrityManager.RequestIntegrityToken(tokenRequest); // Wait for PlayAsyncOperation to complete. yield return requestIntegrityTokenOperation; // Check the resulting error code. if (requestIntegrityTokenOperation.Error != IntegrityErrorCode.NoError) { AppendStatusLog("IntegrityAsyncOperation failed with error: " + requestIntegrityTokenOperation.Error); yield break; } // Get the response. var tokenResponse = requestIntegrityTokenOperation.GetResult(); }
Native
/// Create an IntegrityTokenRequest opaque object. const char* nonce = RequestNonceFromServer(); IntegrityTokenRequest* request; IntegrityTokenRequest_create(&request); IntegrityTokenRequest_setNonce(request, nonce); /// Prepare an IntegrityTokenResponse opaque type pointer and call /// IntegerityManager_requestIntegrityToken(). IntegrityTokenResponse* response; IntegrityErrorCode error_code = IntegrityManager_requestIntegrityToken(request, &response); /// ... /// Proceed to polling iff error_code == INTEGRITY_NO_ERROR if (error_code != INTEGRITY_NO_ERROR) { /// Remember to call the *_destroy() functions. return; } /// ... /// Use polling to wait for the async operation to complete. /// Note, the polling shouldn't block the thread where the IntegrityManager /// is running. IntegrityResponseStatus response_status; /// Check for error codes. IntegrityErrorCode error_code = IntegrityTokenResponse_getStatus(response, &response_status); if (error_code == INTEGRITY_NO_ERROR && response_status == INTEGRITY_RESPONSE_COMPLETED) { const char* integrity_token = IntegrityTokenResponse_getToken(response); SendTokenToServer(integrity_token); } /// ... /// Remember to free up resources. IntegrityTokenRequest_destroy(request); IntegrityTokenResponse_destroy(response); IntegrityManager_destroy();
Mendekripsi dan memverifikasi verdict integritas
Saat Anda meminta verdict integritas, Play Integrity API akan menyediakan token respons yang ditandatangani. Nonce yang Anda sertakan dalam permintaan Anda akan menjadi bagian dari token respons.
Format token
Token berupa JSON Web Token (JWT) bertingkat, yaitu JSON Web Encryption (JWE) dari JSON Web Signature (JWS). Komponen JWE dan JWS direpresentasikan menggunakan serialisasi ringkas.
Algoritma enkripsi dan penandatanganan didukung dengan baik di berbagai implementasi JWT:
Mendekripsi dan memverifikasi di server Google (direkomendasikan)
Play Integrity API memungkinkan Anda mendekripsi dan memverifikasi verdict integritas di server Google, yang meningkatkan keamanan aplikasi. Untuk melakukannya, selesaikan langkah-langkah berikut:
- Buat akun layanan dalam project Google Cloud yang ditautkan ke aplikasi Anda. Selama proses pembuatan akun ini, Anda perlu memberikan peran Service Account User dan Service Usage Consumer kepada akun layanan Anda.
Di server aplikasi Anda, ambil token akses dari kredensial akun layanan menggunakan cakupan
playintegrity
, dan buat permintaan berikut:playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \ '{ "integrity_token": "INTEGRITY_TOKEN" }'
Baca respons JSON.
Mendekripsi dan memverifikasi secara lokal
Jika memilih untuk mengelola dan mendownload kunci enkripsi respons, Anda dapat
mendekripsi dan memverifikasi token yang ditampilkan dalam lingkungan server
Anda yang aman. Anda bisa mendapatkan token yang ditampilkan menggunakan
metode IntegrityTokenResponse#token()
.
Contoh berikut menunjukkan cara mendekode kunci AES dan kunci EC publik yang dienkode DER untuk verifikasi tanda tangan dari Konsol Play ke kunci bahasa tertentu (dalam hal ini, bahasa pemograman Java) di backend aplikasi. Perhatikan bahwa kunci dienkode dengan base64 menggunakan flag default.
Kotlin
// base64OfEncodedDecryptionKey is provided through Play Console. var decryptionKeyBytes: ByteArray = Base64.decode(base64OfEncodedDecryptionKey, Base64.DEFAULT) // Deserialized encryption (symmetric) key. var decryptionKey: SecretKey = SecretKeySpec( decryptionKeyBytes, /* offset= */ 0, AES_KEY_SIZE_BYTES, AES_KEY_TYPE ) // base64OfEncodedVerificationKey is provided through Play Console. var encodedVerificationKey: ByteArray = Base64.decode(base64OfEncodedVerificationKey, Base64.DEFAULT) // Deserialized verification (public) key. var verificationKey: PublicKey = KeyFactory.getInstance(EC_KEY_TYPE) .generatePublic(X509EncodedKeySpec(encodedVerificationKey))
Java
// base64OfEncodedDecryptionKey is provided through Play Console. byte[] decryptionKeyBytes = Base64.decode(base64OfEncodedDecryptionKey, Base64.DEFAULT); // Deserialized encryption (symmetric) key. SecretKey decryptionKey = new SecretKeySpec( decryptionKeyBytes, /* offset= */ 0, AES_KEY_SIZE_BYTES, AES_KEY_TYPE); // base64OfEncodedVerificationKey is provided through Play Console. byte[] encodedVerificationKey = Base64.decode(base64OfEncodedVerificationKey, Base64.DEFAULT); // Deserialized verification (public) key. PublicKey verificationKey = KeyFactory.getInstance(EC_KEY_TYPE) .generatePublic(new X509EncodedKeySpec(encodedVerificationKey));
Berikutnya, gunakan kunci ini untuk mendekripsi token integritas (bagian JWE) terlebih dahulu, lalu verifikasi dan ekstrak bagian JWS bertingkat.
Kotlin
val jwe: JsonWebEncryption = JsonWebStructure.fromCompactSerialization(integrityToken) as JsonWebEncryption jwe.setKey(decryptionKey) // This also decrypts the JWE token. val compactJws: String = jwe.getPayload() val jws: JsonWebSignature = JsonWebStructure.fromCompactSerialization(compactJws) as JsonWebSignature jws.setKey(verificationKey) // This also verifies the signature. val payload: String = jws.getPayload()
Java
JsonWebEncryption jwe = (JsonWebEncryption)JsonWebStructure .fromCompactSerialization(integrityToken); jwe.setKey(decryptionKey); // This also decrypts the JWE token. String compactJws = jwe.getPayload(); JsonWebSignature jws = (JsonWebSignature) JsonWebStructure.fromCompactSerialization(compactJws); jws.setKey(verificationKey); // This also verifies the signature. String payload = jws.getPayload();
Payload yang dihasilkan adalah token teks biasa yang berisi sinyal integritas.
Format payload yang ditampilkan
Payload berupa JSON teks biasa dan berisi sinyal integritas bersama informasi yang disediakan developer.
Berikut adalah struktur payload umum:
{ requestDetails: { ... } appIntegrity: { ... } deviceIntegrity: { ... } accountDetails: { ... } }
Anda harus memastikan terlebih dahulu bahwa nilai di kolom requestDetails
cocok dengan nilai
dari permintaan asli sebelum memeriksa setiap verdict integritas.
Bagian berikut menjelaskan setiap kolom secara lebih mendetail.
Kolom detail permintaan
Kolom requestDetails
berisi informasi yang disediakan dalam permintaan,
termasuk nonce.
requestDetails: { // Application package name this attestation was requested for. // Note that this field might be spoofed in the middle of the // request. requestPackageName: "com.package.name" // base64-encoded URL-safe no-wrap nonce provided by the developer. nonce: "aGVsbG8gd29scmQgdGhlcmU" // The timestamp in milliseconds when the request was made // (computed on the server). timestampMillis: "1617893780" }
Nilai ini harus cocok dengan permintaan asal. Oleh karena itu, verifikasi
bagian requestDetails
payload JSON dengan memastikan bahwa
requestPackageName
dan nonce
cocok dengan yang dikirim dalam permintaan asli, seperti
yang ditunjukkan dalam cuplikan kode:
Kotlin
val requestDetails = JSONObject(payload).getJSONObject("requestDetails") val requestPackageName = requestDetails.getString("requestPackageName") val nonce = requestDetails.getString("nonce") val timestampMillis = requestDetails.getLong("timestampMillis") val currentTimestampMillis = ... // Ensure the token is from your app. if (!requestPackageName.equals(expectedPackageName) // Ensure the token is for this specific request. See “Generate nonce” // section of the doc on how to store/compute the expected nonce. || !nonce.equals(expectedNonce) // Ensure the freshness of the token. || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) { // The token is invalid! See below for further checks. ... }
Java
JSONObject requestDetails = new JSONObject(payload).getJSONObject("requestDetails"); String requestPackageName = requestDetails.getString("requestPackageName"); String nonce = requestDetails.getString("nonce"); long timestampMillis = requestDetails.getLong("timestampMillis"); long currentTimestampMillis = ...; // Ensure the token is from your app. if (!requestPackageName.equals(expectedPackageName) // Ensure the token is for this specific request. See “Generate nonce” // section of the doc on how to store/compute the expected nonce. || !nonce.equals(expectedNonce) // Ensure the freshness of the token. || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) { // The token is invalid! See below for further checks. ... }
Kolom integritas aplikasi
Kolom appIntegrity
berisi informasi terkait paket.
appIntegrity: { // PLAY_RECOGNIZED, UNRECOGNIZED_VERSION, or UNEVALUATED. appRecognitionVerdict: "PLAY_RECOGNIZED" // The package name of the app. // This field is populated iff appRecognitionVerdict != UNEVALUATED. packageName: "com.package.name" // The sha256 digest of app certificates. // This field is populated iff appRecognitionVerdict != UNEVALUATED. certificateSha256Digest: ["6a6a1474b5cbbb2b1aa57e0bc3"] // The version of the app. // This field is populated iff appRecognitionVerdict != UNEVALUATED. versionCode: "42" }
appRecognitionVerdict
dapat memiliki nilai berikut:
PLAY_RECOGNIZED
- Aplikasi dan sertifikat cocok dengan versi yang didistribusikan oleh Google Play.
UNRECOGNIZED_VERSION
- Sertifikat atau nama paket tidak cocok dengan data Google Play.
UNEVALUATED
- Integritas aplikasi tidak dievaluasi. Persyaratan yang diperlukan tidak terpenuhi, seperti perangkat tidak cukup tepercaya.
Untuk memastikan bahwa token dibuat oleh aplikasi yang dibuat oleh Anda, pastikan integritas aplikasi seperti yang diharapkan, seperti yang ditampilkan dalam cuplikan kode berikut:
Kotlin
val requestDetails = JSONObject(payload).getJSONObject("appIntegrity") val appRecognitionVerdict = requestDetails.getString("appRecognitionVerdict") if (appRecognitionVerdict == "PLAY_RECOGNIZED") { // Looks good! }
Java
JSONObject requestDetails = new JSONObject(payload).getJSONObject("appIntegrity"); String appRecognitionVerdict = requestDetails.getString("appRecognitionVerdict"); if (appRecognitionVerdict.equals("PLAY_RECOGNIZED")) { // Looks good! }
Anda juga dapat memeriksa nama paket aplikasi, versi aplikasi, dan sertifikat aplikasi secara manual.
Kolom integritas perangkat
Kolom deviceIntegrity
dapat berisi satu nilai,
deviceRecognitionVerdict
, yang memiliki satu atau beberapa label yang merepresentasikan seberapa baik
perangkat dapat menerapkan integritas aplikasi. Jika perangkat tidak memenuhi kriteria label apa pun,
kolom deviceIntegrity
akan kosong.
deviceIntegrity: { // "MEETS_DEVICE_INTEGRITY" is one of several possible values. deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"] }
Secara default, deviceRecognitionVerdict
dapat memiliki salah satu label berikut:
MEETS_DEVICE_INTEGRITY
- Aplikasi ini berjalan pada perangkat Android yang didukung oleh layanan Google Play. Perangkat ini lulus pemeriksaan integritas sistem dan memenuhi persyaratan kompatibilitas Android.
- Tanpa label (nilai kosong)
- Aplikasi berjalan pada perangkat yang memiliki tanda serangan (seperti hooking API) atau penyusupan sistem (seperti di-root), atau aplikasi tidak berjalan pada perangkat fisik (seperti emulator yang tidak lulus pemeriksaan integritas Google Play).
Untuk memastikan bahwa token berasal dari perangkat tepercaya, pastikan
deviceRecognitionVerdict
sesuai dengan yang diharapkan, seperti ditunjukkan dalam cuplikan
kode berikut:
Kotlin
val deviceIntegrity = JSONObject(payload).getJSONObject("deviceIntegrity") val deviceRecognitionVerdict = if (deviceIntegrity.has("deviceRecognitionVerdict")) { deviceIntegrity.getJSONArray("deviceRecognitionVerdict").toString() } else { "" } if (deviceRecognitionVerdict.contains("MEETS_DEVICE_INTEGRITY")) { // Looks good! }
Java
JSONObject deviceIntegrity = new JSONObject(payload).getJSONObject("deviceIntegrity"); String deviceRecognitionVerdict = deviceIntegrity.has("deviceRecognitionVerdict") ? deviceIntegrity.getJSONArray("deviceRecognitionVerdict").toString() : ""; if (deviceRecognitionVerdict.contains("MEETS_DEVICE_INTEGRITY")) { // Looks good! }
Jika Anda mengalami masalah dengan pengujian perangkat memenuhi integritas perangkat, pastikan ROM pabrik sudah diinstal (misalnya, dengan mereset perangkat) dan bootloader terkunci. Anda juga dapat membuat pengujian Integritas Play di Konsol Play.
Jika Anda memilih untuk menerima label tambahan dalam verdict integritas,
deviceRecognitionVerdict
dapat memiliki label tambahan berikut:
MEETS_BASIC_INTEGRITY
- Aplikasi berjalan di perangkat yang telah lulus pemeriksaan integritas sistem dasar. Perangkat mungkin tidak memenuhi persyaratan kompatibilitas Android dan mungkin tidak disetujui untuk menjalankan layanan Google Play. Misalnya, perangkat mungkin menjalankan versi Android yang tidak dikenal, mungkin memiliki bootloader yang tidak terkunci, atau mungkin belum disertifikasi oleh produsen.
MEETS_STRONG_INTEGRITY
- Aplikasi tersebut berjalan di perangkat Android yang didukung oleh layanan Google Play dan memiliki jaminan kuat atas integritas sistem seperti bukti integritas booting yang didukung hardware. Perangkat lulus pemeriksaan integritas sistem dan memenuhi persyaratan kompatibilitas Android.
Selain itu, jika aplikasi Anda dirilis ke emulator yang disetujui,
deviceRecognitionVerdict
juga dapat menggunakan label berikut:
MEETS_VIRTUAL_INTEGRITY
- Aplikasi berjalan di Android emulator yang didukung oleh layanan Google Play. Emulator lulus pemeriksaan integritas sistem dan memenuhi persyaratan kompatibilitas inti Android.
Kolom detail akun
Kolom accountDetails
berisi satu nilai, appLicensingVerdict
, yang
menunjukkan status pemberian lisensi/hak aplikasi.
accountDetails: { // This field can be LICENSED, UNLICENSED, or UNEVALUATED. appLicensingVerdict: "LICENSED" }
appLicensingVerdict
dapat memiliki nilai berikut:
LICENSED
- Pengguna memiliki hak aplikasi. Dengan kata lain, pengguna menginstal atau membeli aplikasi Anda di Google Play.
UNLICENSED
- Pengguna tidak memiliki hak aplikasi. Hal ini terjadi jika, misalnya, pengguna melakukan sideload aplikasi Anda atau tidak mendapatkan aplikasi dari Google Play.
UNEVALUATED
Detail pemberian lisensi tidak dievaluasi karena persyaratan yang diperlukan tidak terjawab.
Hal ini dapat terjadi karena beberapa alasan, termasuk:
- Perangkat tidak cukup tepercaya.
- Versi aplikasi yang diinstal di perangkat tidak dikenal oleh Google Play.
- Pengguna tidak login ke Google Play.
Untuk memeriksa apakah pengguna memiliki hak aplikasi untuk aplikasi Anda, verifikasi bahwa
appLicensingVerdict
sudah sesuai harapan, seperti yang ditunjukkan dalam cuplikan kode berikut:
Kotlin
val requestDetails = JSONObject(payload).getJSONObject("accountDetails") val appLicensingVerdict = requestDetails.getString("appLicensingVerdict") if (appLicensingVerdict == "LICENSED") { // Looks good! }
Java
JSONObject requestDetails = new JSONObject(payload).getJSONObject("accountDetails"); String appLicensingVerdict = requestDetails.getString("appLicensingVerdict"); if (appLicensingVerdict.equals("LICENSED")) { // Looks good! }