.NET MAUI应用安全与身份验证完全指南:从OAuth 2.0到代码保护实战

从OAuth 2.0+PKCE身份验证、SecureStorage安全存储、Certificate Pinning证书固定到NativeAOT编译和代码混淆,全面覆盖.NET MAUI移动应用安全的每个关键层面,附完整代码示例和平台配置。

为什么你的.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流程其实不复杂,分为这几步:

  1. 客户端生成一个随机字符串code_verifier
  2. code_verifier做SHA-256哈希后Base64编码,得到code_challenge
  3. code_challenge随授权请求一起发送给认证服务器
  4. 服务器返回授权码(authorization code)
  5. 客户端用授权码 + 原始code_verifier换取access token
  6. 服务器验证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层面做额外安全检查)组合起来,构建纵深防御才是正道。

关于作者 Editorial Team

Our team of expert writers and editors.