为什么你的.NET MAUI应用需要认真对待安全?
做移动端开发的同学可能都有个误区——觉得安全是后端的事,前端只要调调API就行了。说实话,我之前也这么想过。但现实给了我一巴掌:移动应用是攻击者最喜欢的突破口之一。
.NET MAUI应用编译后的程序集包含丰富的元数据和IL中间代码,用ILSpy这类工具几分钟就能反编译出来。如果你的应用涉及用户隐私、支付流程或者企业数据,安全绝不是可选项。(别问我怎么知道的,问就是被甲方安全审计教做人了。)
2026年的移动安全形势更加严峻了。根据OWASP Mobile Top 10,最常见的安全风险包括:不安全的认证机制、不当的凭据存储、不充分的输入验证以及代码逆向工程。
所以这篇文章会从身份验证、安全存储、网络通信、代码保护四个维度,给你一套完整的.NET MAUI安全实践方案。每个部分都有可以直接拿来用的代码,不搞纸上谈兵。
OAuth 2.0 + PKCE身份验证:用WebAuthenticator实现安全登录
现代移动应用的身份验证标准是OAuth 2.0 + Authorization Code + PKCE。PKCE(Proof Key for Code Exchange)是专门为移动端和公共客户端设计的安全扩展——简单来说,它通过生成一对code_verifier和code_challenge来防止授权码被截获后滥用。
PKCE工作原理
整个PKCE流程其实不复杂,分为这几步:
- 客户端生成一个随机字符串
code_verifier - 对
code_verifier做SHA-256哈希后Base64编码,得到code_challenge - 将
code_challenge随授权请求一起发送给认证服务器 - 服务器返回授权码(authorization code)
- 客户端用授权码 + 原始
code_verifier换取access token - 服务器验证
code_verifier的哈希是否匹配之前收到的code_challenge
关键点在于:即使攻击者拦截了授权码,没有code_verifier也换不到token。这就是目前移动端认证的最佳实践。
实现PKCE辅助类
先封装一个PKCE工具类,后面会反复用到:
using System.Security.Cryptography;
using System.Text;
namespace MyMauiApp.Security;
public static class PkceHelper
{
public static string CreateCodeVerifier()
{
var bytes = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(bytes);
return Convert.ToBase64String(bytes)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
}
public static string CreateCodeChallenge(string codeVerifier)
{
using var sha256 = SHA256.Create();
var challengeBytes = sha256.ComputeHash(
Encoding.ASCII.GetBytes(codeVerifier));
return Convert.ToBase64String(challengeBytes)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
}
}
使用WebAuthenticator发起OAuth登录
.NET MAUI内置了WebAuthenticator API,它会启动系统浏览器完成认证流程,然后通过自定义URI scheme把结果回调给应用。这比在WebView里做登录安全太多了:
using Microsoft.Maui.Authentication;
namespace MyMauiApp.Services;
public class AuthService
{
private const string AuthorizeUrl = "https://your-auth-server.com/oauth2/authorize";
private const string TokenUrl = "https://your-auth-server.com/oauth2/token";
private const string ClientId = "your-client-id";
private const string RedirectUri = "myapp://callback";
private const string Scope = "openid profile email";
private string? _codeVerifier;
public async Task LoginAsync()
{
_codeVerifier = PkceHelper.CreateCodeVerifier();
var codeChallenge = PkceHelper.CreateCodeChallenge(_codeVerifier);
var state = Guid.NewGuid().ToString("N");
var authUrl = $"{AuthorizeUrl}?" +
$"client_id={ClientId}" +
$"&redirect_uri={Uri.EscapeDataString(RedirectUri)}" +
$"&response_type=code" +
$"&scope={Uri.EscapeDataString(Scope)}" +
$"&code_challenge={codeChallenge}" +
$"&code_challenge_method=S256" +
$"&state={state}";
try
{
var result = await WebAuthenticator.Default.AuthenticateAsync(
new Uri(authUrl),
new Uri(RedirectUri));
var code = result?.Get("code");
if (string.IsNullOrEmpty(code))
return null;
return await ExchangeCodeForTokenAsync(code);
}
catch (TaskCanceledException)
{
// 用户取消了登录
return null;
}
}
private async Task ExchangeCodeForTokenAsync(string code)
{
using var httpClient = new HttpClient();
var tokenRequest = new Dictionary
{
["grant_type"] = "authorization_code",
["code"] = code,
["redirect_uri"] = RedirectUri,
["client_id"] = ClientId,
["code_verifier"] = _codeVerifier!
};
var response = await httpClient.PostAsync(
TokenUrl,
new FormUrlEncodedContent(tokenRequest));
if (!response.IsSuccessStatusCode)
return null;
var json = await response.Content.ReadAsStringAsync();
var tokenResponse = System.Text.Json.JsonSerializer
.Deserialize(json);
// 安全存储token
await SecureStorage.Default.SetAsync(
"access_token", tokenResponse!.AccessToken);
await SecureStorage.Default.SetAsync(
"refresh_token", tokenResponse.RefreshToken);
return tokenResponse.AccessToken;
}
}
public class TokenResponse
{
[System.Text.Json.Serialization.JsonPropertyName("access_token")]
public string AccessToken { get; set; } = "";
[System.Text.Json.Serialization.JsonPropertyName("refresh_token")]
public string RefreshToken { get; set; } = "";
[System.Text.Json.Serialization.JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
}
Android平台配置:Intent Filter
Android端需要注册一个Activity来处理回调URI。在Platforms/Android目录下创建就行:
using Android.App;
using Android.Content.PM;
namespace MyMauiApp.Platforms.Android;
[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)]
[IntentFilter(new[] { global::Android.Content.Intent.ActionView },
Categories = new[] {
global::Android.Content.Intent.CategoryDefault,
global::Android.Content.Intent.CategoryBrowsable
},
DataScheme = "myapp")]
public class WebAuthCallbackActivity
: Microsoft.Maui.Authentication.WebAuthenticatorCallbackActivity
{
}
iOS平台配置:URL Scheme
iOS这边需要在Info.plist里注册自定义URI scheme:
CFBundleURLTypes
CFBundleURLSchemes
myapp
同时在AppDelegate.cs中确保正确处理回调:
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
public override bool OpenUrl(UIApplication application,
NSUrl url, NSDictionary options)
{
if (Platform.OpenUrl(application, url, options))
return true;
return base.OpenUrl(application, url, options);
}
}
MSAL集成:微软身份平台认证
如果你的应用需要接入Microsoft Entra ID(原Azure AD)做企业级身份验证,那MSAL.NET(Microsoft Authentication Library)是官方推荐方案。老实说,它帮你处理了token获取、缓存、刷新这些烦人的事情,省了不少心。
配置MSAL
先装NuGet包:
dotnet add package Microsoft.Identity.Client
然后封装一个单例包装器。注意看代码里针对Android和iOS的平台差异处理:
using Microsoft.Identity.Client;
namespace MyMauiApp.Security;
public class MsalAuthService
{
private static readonly Lazy _instance =
new(() => new MsalAuthService());
public static MsalAuthService Instance => _instance.Value;
private IPublicClientApplication _pca;
private const string ClientId = "your-azure-client-id";
private const string TenantId = "your-tenant-id";
private const string Authority =
$"https://login.microsoftonline.com/{TenantId}";
private static readonly string[] Scopes =
new[] { "User.Read", "openid", "profile" };
private MsalAuthService()
{
_pca = PublicClientApplicationBuilder
.Create(ClientId)
.WithAuthority(Authority)
.WithRedirectUri($"msal{ClientId}://auth")
#if ANDROID
.WithParentActivityOrWindow(() =>
Platform.CurrentActivity!)
#elif IOS
.WithIosKeychainSecurityGroup(
"com.yourcompany.myapp")
#endif
.Build();
}
public async Task SignInAsync()
{
try
{
// 先尝试静默获取(使用缓存的token)
var accounts = await _pca.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
if (firstAccount != null)
{
return await _pca
.AcquireTokenSilent(Scopes, firstAccount)
.ExecuteAsync();
}
}
catch (MsalUiRequiredException)
{
// 需要用户交互
}
// 交互式登录
return await _pca
.AcquireTokenInteractive(Scopes)
.ExecuteAsync();
}
public async Task SignOutAsync()
{
var accounts = await _pca.GetAccountsAsync();
foreach (var account in accounts)
{
await _pca.RemoveAsync(account);
}
}
}
在ViewModel中使用MSAL
实际用起来很简单,配合CommunityToolkit.Mvvm几行代码搞定:
public partial class LoginViewModel : ObservableObject
{
[ObservableProperty]
private string userName = "";
[ObservableProperty]
private bool isLoggedIn;
[RelayCommand]
private async Task LoginAsync()
{
var result = await MsalAuthService.Instance.SignInAsync();
if (result != null)
{
UserName = result.Account.Username;
IsLoggedIn = true;
// 安全存储access token供API调用使用
await SecureStorage.Default.SetAsync(
"access_token", result.AccessToken);
}
}
[RelayCommand]
private async Task LogoutAsync()
{
await MsalAuthService.Instance.SignOutAsync();
SecureStorage.Default.Remove("access_token");
UserName = "";
IsLoggedIn = false;
}
}
SecureStorage安全存储:别把Token随便放
token怎么存,这个问题真的很关键。
我见过不少项目直接用Preferences甚至文件系统存token——这是非常不安全的做法。Android上root一下就能看到明文,iOS上越狱后也一样。.NET MAUI提供了SecureStorage,它在不同平台用的是操作系统级别的加密机制:
- Android:使用
EncryptedSharedPreferences,基于AES-256-GCM加密 - iOS:使用Keychain,这是iOS系统最安全的存储方式
- Windows:打包应用使用
ApplicationData.Current.LocalSettings,数据经DPAPI加密
封装安全Token管理器
下面这个封装类帮你处理了token过期检查和自动刷新的逻辑,算是一个比较实用的起点:
namespace MyMauiApp.Security;
public class SecureTokenManager
{
private const string AccessTokenKey = "access_token";
private const string RefreshTokenKey = "refresh_token";
private const string TokenExpiryKey = "token_expiry";
public async Task StoreTokensAsync(
string accessToken,
string refreshToken,
DateTimeOffset expiry)
{
await SecureStorage.Default.SetAsync(
AccessTokenKey, accessToken);
await SecureStorage.Default.SetAsync(
RefreshTokenKey, refreshToken);
await SecureStorage.Default.SetAsync(
TokenExpiryKey, expiry.ToUnixTimeSeconds().ToString());
}
public async Task GetAccessTokenAsync()
{
var expiryStr = await SecureStorage.Default.GetAsync(
TokenExpiryKey);
if (!string.IsNullOrEmpty(expiryStr)
&& long.TryParse(expiryStr, out var unixTime))
{
var expiry = DateTimeOffset.FromUnixTimeSeconds(unixTime);
if (expiry < DateTimeOffset.UtcNow.AddMinutes(-5))
{
// Token即将过期或已过期,尝试刷新
return await RefreshAccessTokenAsync();
}
}
return await SecureStorage.Default.GetAsync(AccessTokenKey);
}
private async Task RefreshAccessTokenAsync()
{
var refreshToken = await SecureStorage.Default.GetAsync(
RefreshTokenKey);
if (string.IsNullOrEmpty(refreshToken))
return null;
// 调用token端点用refresh_token换取新的access_token
// 具体实现取决于你的认证服务器
// ...
return null;
}
public void ClearAllTokens()
{
SecureStorage.Default.Remove(AccessTokenKey);
SecureStorage.Default.Remove(RefreshTokenKey);
SecureStorage.Default.Remove(TokenExpiryKey);
}
}
Android Auto Backup的坑
这里有个很多人会踩的坑。Android 6.0+默认会自动备份应用数据,包括SharedPreferences。问题来了:当用户换机恢复数据时,加密的SecureStorage数据在新设备上是无法解密的——因为加密密钥是设备绑定的。结果就是应用直接崩溃。
解决方法是在AndroidManifest.xml中排除这些数据:
然后创建Platforms/Android/Resources/xml/auto_backup_rules.xml:
API通信安全:Certificate Pinning与HttpClient配置
即使用了HTTPS,如果用户设备被装了恶意CA证书(企业设备上挺常见的),中间人攻击依然有可能发生。Certificate Pinning(证书固定)就是为了防这个——让你的应用只信任特定的服务器证书或公钥。
Android平台实现:Network Security Config
Android原生就支持通过Network Security Configuration做证书固定,配置起来很方便。创建Platforms/Android/Resources/xml/network_security_config.xml:
api.yourserver.com
base64编码的证书公钥SHA-256哈希值=
备份证书的SHA-256哈希值=
然后在AndroidManifest.xml中引用一下就好:
iOS平台实现
iOS这边稍微麻烦一点,需要通过自定义HttpMessageHandler来做:
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace MyMauiApp.Platforms.iOS;
public class PinnedHttpHandler : NSUrlSessionHandler
{
private readonly HashSet _pinnedKeys;
public PinnedHttpHandler(IEnumerable pinnedPublicKeyHashes)
{
_pinnedKeys = new HashSet(pinnedPublicKeyHashes);
TrustOverrideForUrl = (sender, url, trust) =>
{
if (trust == null) return false;
var serverCert = trust.GetResult();
// 验证服务器证书的公钥哈希
// 与预存的pin值比较
return ValidatePins(trust);
};
}
private bool ValidatePins(Security.SecTrust trust)
{
var certCount = trust.Count;
for (int i = 0; i < certCount; i++)
{
var cert = trust[i];
var publicKey = cert.GetPublicKey();
// 计算公钥的SHA-256并与pinnedKeys比较
// 匹配任意一个即可
}
return false;
}
}
构建安全的HttpClient
接下来把上面的安全策略整合到一个统一的HttpClient工厂里。这样整个应用只需要注入这个client就行了:
namespace MyMauiApp.Services;
public static class SecureHttpClientFactory
{
public static HttpClient CreateSecureClient(
SecureTokenManager tokenManager)
{
HttpMessageHandler handler;
#if ANDROID
handler = new Xamarin.Android.Net.AndroidMessageHandler
{
// Android自动使用network_security_config
};
#elif IOS
handler = new Platforms.iOS.PinnedHttpHandler(
new[] { "你的证书pin值" });
#else
handler = new HttpClientHandler();
#endif
var client = new HttpClient(handler)
{
BaseAddress = new Uri("https://api.yourserver.com"),
Timeout = TimeSpan.FromSeconds(30)
};
// 禁止跟随重定向到非HTTPS地址
if (handler is HttpClientHandler httpHandler)
{
httpHandler.AllowAutoRedirect = true;
httpHandler.MaxAutomaticRedirections = 3;
}
return client;
}
}
// 在DI容器中注册
// MauiProgram.cs
builder.Services.AddSingleton();
builder.Services.AddSingleton(sp =>
SecureHttpClientFactory.CreateSecureClient(
sp.GetRequiredService()));
自动附加Bearer Token的DelegatingHandler
每个API请求都手动加token?那也太累了。写一个DelegatingHandler自动搞定:
public class AuthenticatedHttpHandler : DelegatingHandler
{
private readonly SecureTokenManager _tokenManager;
public AuthenticatedHttpHandler(
SecureTokenManager tokenManager,
HttpMessageHandler innerHandler) : base(innerHandler)
{
_tokenManager = tokenManager;
}
protected override async Task SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var token = await _tokenManager.GetAccessTokenAsync();
if (!string.IsNullOrEmpty(token))
{
request.Headers.Authorization =
new System.Net.Http.Headers
.AuthenticationHeaderValue("Bearer", token);
}
var response = await base.SendAsync(
request, cancellationToken);
// 401时清除token,提示用户重新登录
if (response.StatusCode ==
System.Net.HttpStatusCode.Unauthorized)
{
_tokenManager.ClearAllTokens();
// 触发重新登录流程
}
return response;
}
}
代码保护与反逆向工程
.NET MAUI应用编译后的DLL包含完整的元数据和IL代码,攻击者用ILSpy之类的工具几分钟就能看到你的源码。这不是吓唬你——我亲手试过反编译自己的项目,出来的代码几乎跟原始版本一模一样。
对于涉及商业机密、加密逻辑或许可证验证的应用,代码保护是必须做的。
多层防御策略
有效的代码保护不是靠单一手段,而是多层叠加:
- 代码混淆(Obfuscation)——重命名类、方法、字段,打乱控制流
- 字符串加密(String Encryption)——防止敏感字符串被直接搜索到
- NativeAOT编译——直接编译为原生机器码,压根没有IL可以反编译
- 篡改检测(Tamper Detection)——运行时校验应用完整性
- Root/越狱检测——发现设备被破解时采取保护措施
NativeAOT:最强编译级保护
.NET 10对MAUI的NativeAOT支持已经相当成熟了。NativeAOT会把C#代码直接编译为原生机器码,完全不包含IL代码和元数据,从根本上堵死了反编译这条路。
在项目文件中启用也很简单:
true
-all
NativeAOT除了安全性,还有个好处:启动速度和内存占用都有明显改善。实测.NET 10 + NativeAOT的启动时间能缩短30%-50%,算是意外之喜。
代码混淆工具集成
即使用了NativeAOT,对于无法完全AOT编译的场景(比如需要反射的部分),代码混淆仍然很有必要。目前主流的工具有:
- Dotfuscator(PreEmptive):微软官方推荐,支持重命名、控制流混淆、字符串加密、篡改检测
- Babel Obfuscator:比较轻量,对.NET MAUI项目兼容性不错
- ByteHide Shield:提供代码虚拟化等高级保护功能
以Dotfuscator为例,在MSBuild中集成:
Root/越狱检测
在被root或越狱的设备上,应用更容易被调试和篡改。虽然这不是万能的(后面FAQ会讲到),但作为防御层之一还是值得加上。
namespace MyMauiApp.Security;
public static class DeviceSecurityChecker
{
public static bool IsDeviceCompromised()
{
#if ANDROID
return CheckAndroidRoot();
#elif IOS
return CheckIosJailbreak();
#else
return false;
#endif
}
#if ANDROID
private static bool CheckAndroidRoot()
{
var rootIndicators = new[]
{
"/system/app/Superuser.apk",
"/system/xbin/su",
"/system/bin/su",
"/sbin/su",
"/data/local/bin/su",
"/data/local/xbin/su"
};
foreach (var path in rootIndicators)
{
if (System.IO.File.Exists(path))
return true;
}
// 检查危险属性
try
{
var process = Java.Lang.Runtime.GetRuntime()?
.Exec(new[] { "getprop", "ro.debuggable" });
if (process != null)
{
using var reader = new System.IO.StreamReader(
process.InputStream!);
var result = reader.ReadToEnd().Trim();
if (result == "1") return true;
}
}
catch { }
return false;
}
#endif
#if IOS
private static bool CheckIosJailbreak()
{
var jailbreakPaths = new[]
{
"/Applications/Cydia.app",
"/Library/MobileSubstrate/MobileSubstrate.dylib",
"/bin/bash",
"/usr/sbin/sshd",
"/etc/apt",
"/private/var/lib/apt/"
};
foreach (var path in jailbreakPaths)
{
if (System.IO.File.Exists(path))
return true;
}
// 检查是否能写入系统目录
try
{
System.IO.File.WriteAllText(
"/private/test_jailbreak.txt", "test");
System.IO.File.Delete("/private/test_jailbreak.txt");
return true; // 如果能写入说明越狱了
}
catch { }
return false;
}
#endif
}
在应用启动时调用检测:
// App.xaml.cs
protected override void OnStart()
{
if (DeviceSecurityChecker.IsDeviceCompromised())
{
// 根据业务需求选择策略:
// 1. 显示警告但允许继续
// 2. 禁用敏感功能
// 3. 直接退出应用
MainPage = new ContentPage
{
Content = new Label
{
Text = "检测到不安全设备,部分功能已限制",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}
};
}
}
生物识别认证:指纹和面部识别
对于支付确认、敏感数据查看这类操作,光有OAuth登录还不够,再加一层生物识别会安心很多。.NET MAUI本身没有内置生物识别API,不过社区有很好用的Plugin.Maui.Biometric包:
// 安装 Plugin.Maui.Biometric NuGet包
// dotnet add package Plugin.Maui.Biometric
using Plugin.Maui.Biometric;
namespace MyMauiApp.Services;
public class BiometricService
{
private readonly IBiometric _biometric;
public BiometricService(IBiometric biometric)
{
_biometric = biometric;
}
public async Task AuthenticateAsync(string reason)
{
var request = new AuthenticationRequest
{
Title = "身份验证",
Subtitle = reason,
NegativeText = "取消"
};
var result = await _biometric.AuthenticateAsync(
request, CancellationToken.None);
return result.Status == BiometricResponseStatus.Success;
}
public async Task IsBiometricAvailableAsync()
{
var result = await _biometric.GetAuthenticationStatusAsync();
return result == BiometricHwStatus.Success;
}
}
// MauiProgram.cs中注册
builder.Services.AddSingleton(BiometricAuthenticationService.Default);
builder.Services.AddSingleton();
安全架构最佳实践总结
好了,到这里各个安全层面都讲到了。来把它们整合到依赖注入容器中,看看完整的样子:
// MauiProgram.cs — 完整安全配置
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSans");
});
// 安全服务注册
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
// 安全HttpClient注册
builder.Services.AddSingleton(sp =>
{
var tokenManager = sp.GetRequiredService();
return SecureHttpClientFactory.CreateSecureClient(tokenManager);
});
// ViewModels
builder.Services.AddTransient();
return builder.Build();
}
完整的安全防护体系应该覆盖下面这些层面:
| 安全层面 | 方案 | 关键技术 |
|---|---|---|
| 身份验证 | OAuth 2.0 + PKCE | WebAuthenticator、MSAL |
| 凭据存储 | 操作系统级加密 | SecureStorage(Keychain/EncryptedSharedPrefs) |
| 网络通信 | Certificate Pinning | Network Security Config、NSUrlSessionHandler |
| API安全 | Bearer Token自动管理 | DelegatingHandler、Token刷新 |
| 代码保护 | 多层混淆 + AOT | NativeAOT、Dotfuscator |
| 运行时保护 | 环境检测 | Root/越狱检测、篡改检测 |
| 二次验证 | 生物识别 | 指纹/面部识别 |
常见问题(FAQ)
.NET MAUI应用应该使用哪种身份验证方式?
推荐OAuth 2.0 + Authorization Code + PKCE,这是OWASP和RFC 8252推荐的移动端标准方案。如果后端是Microsoft Entra ID,直接用MSAL.NET最省事——token管理、缓存、刷新它全包了。自建认证服务器的话,WebAuthenticator + PKCE就够了。
SecureStorage的数据在换机后能恢复吗?
不能直接恢复。Android的EncryptedSharedPreferences和iOS的Keychain加密密钥都是绑定到具体设备的,换了设备密钥就不同了,旧数据解不开。正确的做法是设计好token刷新机制,让用户在新设备重新登录就行。别忘了配置auto_backup_rules.xml排除加密数据,不然备份恢复后应用会崩。
NativeAOT编译对.NET MAUI应用有什么限制?
最大的限制是不支持运行时动态代码生成(Reflection.Emit),某些重度依赖反射的库可能需要调整或者显式标注保留类型。好消息是截至.NET 10,NativeAOT在iOS上已经比较成熟了,Android上也可以用。启用后应用体积可能变大,但启动速度会有明显提升,一般来说这个取舍是值得的。
如何防止.NET MAUI应用被中间人攻击?
Certificate Pinning是最有效的防御手段。Android上用network_security_config.xml声明pin值,iOS上通过自定义HttpMessageHandler验证服务器证书公钥。另外别忘了:始终用HTTPS、禁止明文流量、在pin配置里包含备份pin以应对证书轮换、定期更新pin值并设好过期时间。
Root或越狱检测被绕过了怎么办?
说实话,Root/越狱检测本来就不是银弹。高级攻击者可以hook检测函数轻松绕过。所以它应该是多层防御中的一部分,而不是你唯一的依仗。把代码混淆(让攻击者难以找到检测逻辑)、运行时完整性校验(检测代码是否被篡改)和服务端验证(在API层面做额外安全检查)组合起来,构建纵深防御才是正道。