Xamarin.Forms调用SafetyNet Api Attestation无法获取返回响应如何解决
问题排查及修复方案
现有代码核心问题
- 主线程死锁:你在
OnCreate(Android主线程执行)中调用Task.Run(() => RunSafetyNetCheck(client, nonce)).Result,Result会阻塞主线程等待异步任务返回,而await client.AttestAsync执行完成后需要回传上下文到主线程,导致互相等待的死锁,自然拿不到返回结果。 - 业务逻辑倒置:
RunSafetyNetCheck返回true代表验证通过,你现有代码中if (!valid) LoadApplication(new App())逻辑完全写反,会导致验证通过的设备反而没法进入应用。 - 校验字段缺失:你只校验了
ctsProfileMatch,缺少basicIntegrity字段的判断,这个字段才是直接标识设备是否root/运行在模拟器的核心指标,ctsProfileMatch是额外的系统兼容性认证校验。 - 缺少权限和配置校验:没有检查网络权限、Google Play服务权限,也没有提前校验API密钥是否正确配置。
修复步骤
1. 提前配置校验
- 首先确认你已在Google Cloud控制台启用SafetyNet Attestation API,并且在API密钥的限制中添加了你应用包名对应的SHA-256签名指纹,否则请求会被Google直接拒绝。
- 在
AndroidManifest.xml中添加必要权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
2. 修复死锁和逻辑问题
不要在主线程用.Result阻塞,把SafetyNet校验改成异步执行,完成后再加载应用,修正后的代码如下:
protected override void OnCreate(Bundle bundle) { Instance = this; TabLayoutResource = Resource.Layout.Tabbar; ToolbarResource = Resource.Layout.Toolbar; BackgroundAggregator.Init(this); base.OnCreate(bundle); DependencyService.Register<ToastNotification>(); ToastNotification.Init(this); Rg.Plugins.Popup.Popup.Init(this); global::Xamarin.Forms.Forms.Init(this, bundle); micService = DependencyService.Resolve<IMicrophoneService>(); if (GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this, 13000000) == ConnectionResult.Success) { // 异步执行校验,不阻塞主线程 _ = CheckSafetyNetAsync(); } else { Toast.MakeText(this, "请更新Google Play服务", ToastLength.Short).Show(); // 可选:如果要求必须支持SafetyNet,这里可以直接退出应用 LoadApplication(new App()); } } private async Task CheckSafetyNetAsync() { SafetyNetClient client = Android.Gms.SafetyNet.SafetyNetClass.GetClient(this); byte[] nonce = Android.Gms.SafetyNet.Nonce.Generate(); bool isValid = await RunSafetyNetCheck(client, nonce); if (isValid) { // 校验通过,加载应用 LoadApplication(new App()); } else { Toast.MakeText(this, "设备环境不安全,无法运行应用", ToastLength.Long).Show(); // 可选:退出应用 Finish(); } } private async Task<bool> RunSafetyNetCheck(SafetyNetClient client, byte[] nonce) { try { SafetyNetApiAttestationResponse r = await client.AttestAsync(nonce, apiKey); if (r == null || string.IsNullOrEmpty(r.JwsResult)) { Toast.MakeText(this, "兼容性检查失败", ToastLength.Long).Show(); return false; } attestationResponse = r; var decodedResult = r.DecodeJwsResult(nonce); string error = null; if (VerifyAttestResponse(decodedResult, nonce, out error)) { return await attestationResponse.ValidateWithGoogle(apiKey); } else { Toast.MakeText(this, "兼容性不通过: " + error, ToastLength.Long).Show(); return false; } } catch (Exception ex) { Toast.MakeText(this, "校验出错: " + ex.Message, ToastLength.Long).Show(); return false; } } private bool VerifyAttestResponse(string data, byte[] sentNonce, out string errorMessage) { errorMessage = null; var json = JsonObject.Parse(data); var error = GetValue(json, "error"); if (!string.IsNullOrEmpty(error)) { errorMessage = "响应返回错误: " + error; return false; } var nonce = GetValue(json, "nonce"); var sentNonceStr = Convert.ToBase64String(sentNonce); if (!nonce.Equals(sentNonceStr)) { errorMessage = "随机数校验失败"; return false; } if (PackageName != GetValue(json, "apkPackageName")) { errorMessage = "包名校验失败"; return false; } // 新增基础完整性校验,判断root/模拟器 bool basicIntegrity = GetValue(json, "basicIntegrity") == "true"; if (!basicIntegrity) { errorMessage = "设备已root或运行在模拟器环境"; return false; } // 可选:如果需要严格校验系统合规性,保留ctsProfileMatch判断 bool ctsProfileMatch = GetValue(json, "ctsProfileMatch") == "true"; if (!ctsProfileMatch) { errorMessage = "设备未通过CTS认证"; return false; } return true; } private string GetValue(JsonValue json, string field) { return json.ContainsKey(field) ? json[field].ToString().Trim('"') : string.Empty; }
3. 额外说明
目前Google已正式停用SafetyNet Attestation API,建议你后续迁移到Play Integrity API,新的API对设备环境的判断准确率更高,也不会有废弃风险。
内容的提问来源于stack exchange,提问作者thilim9
相关产品推荐
相关产品推荐

