Loading...
正在加载...
请稍候

第十三章:企业级应用:身份验证与安全

✨步子哥 (steper) 2026年02月17日 05:28
# 第十三章:企业级应用:身份验证与安全 > **本章导读**:想象你正在设计一座城堡的入口系统。你需要确认每一位访客的身份(身份验证),然后决定他们可以进入哪些房间(授权)。在数字世界中,这两项任务构成了应用安全的第一道防线。现代应用不再自己存储用户密码——那就像在城堡门口放一串钥匙一样危险。相反,我们将身份验证委托给专业的"守门人":IdentityServer、Auth0、Azure AD 或其他身份提供者。本章将带你穿越 OAuth2 和 OIDC 的迷宫,在 Uno Platform 中构建坚不可摧的认证系统。 --- ## 🔐 13.1 安全性:企业级应用的基石 在深入技术细节之前,让我们先明确两个核心概念:**身份验证(Authentication)** 和 **授权(Authorization)**。这两个词看起来很像,但它们的含义截然不同。 身份验证回答的问题是"你是谁?"——验证用户的身份,确认他们就是声称的那个人。这就像在机场安检时出示护照,工作人员核对你的照片和姓名。 授权回答的问题是"你能做什么?"——确定已认证用户可以访问哪些资源、执行哪些操作。这就像护照盖上的签证,它决定了你可以进入哪些国家。 > **第一性原理**:为什么要将身份验证委托给第三方服务?答案在于**安全专业主义**。身份管理是一个极其复杂的领域——密码哈希、防暴力破解、多因素认证、令牌刷新、安全审计——每一个环节都需要专业知识。与其自己构建一个可能有漏洞的系统,不如使用那些经过时间检验、由安全专家维护的服务。这就像你不会自己建造银行金库,而是把钱存在专业的银行里一样。 ### 🌐 13.1.1 现代认证架构概览 在传统的 Web 应用中,认证相对简单:用户提交用户名和密码,服务器验证后创建一个会话(Session),并在浏览器中设置一个 Cookie。但在移动应用和跨平台应用中,情况变得复杂得多。 首先,移动应用不能依赖 Cookie——它们需要使用令牌(Token)来维护认证状态。其次,用户可能希望在多个设备上登录,每个设备都需要独立的令牌管理。最后,出于安全考虑,我们不应该在应用中直接处理用户密码——密码应该只由用户输入到身份提供者的登录页面。 现代认证架构通常采用 **OAuth 2.0** 和 **OpenID Connect (OIDC)** 协议。这些协议定义了一套标准的流程,让应用能够安全地获取用户身份,而无需直接接触用户的凭据。 ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 用户 │ │ 应用 │ │ 身份提供者 │ │ (Resource │ │ (Client) │ │ (IdP) │ │ Owner) │ │ │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ 1. 点击"登录" │ │ │─────────────────────>│ │ │ │ │ │ 2. 打开浏览器登录页 │ │ │<─────────────────────│ │ │ │ │ │ 3. 输入用户名密码 │ │ │─────────────────────────────────────────────>│ │ │ │ │ 4. 授权确认 │ │ │─────────────────────────────────────────────>│ │ │ │ │ 5. 授权码回调 │ │ │<─────────────────────────────────────────────│ │ │ │ │ 6. 传递授权码 │ │ │─────────────────────>│ │ │ │ │ │ │ 7. 用授权码换取令牌 │ │ │─────────────────────>│ │ │ │ │ │ 8. 返回访问令牌 │ │ │<─────────────────────│ │ │ │ │ 9. 登录成功 │ │ │<─────────────────────│ │ ``` --- ## 🔑 13.2 现代认证协议:OIDC 与 OAuth2 深入解析 在开始编写代码之前,我们需要深入理解 OAuth 2.0 和 OpenID Connect 的工作原理。这两个协议构成了现代身份验证的基础。 ### 📖 13.2.1 OAuth 2.0:授权框架 OAuth 2.0 是一个授权框架,它允许第三方应用在用户授权下,访问用户在某个服务上的资源,而无需用户提供密码。OAuth 2.0 定义了四种角色: **资源所有者(Resource Owner)**:通常是用户本人,拥有对受保护资源的访问权。 **客户端(Client)**:你的应用,想要访问用户的资源。 **授权服务器(Authorization Server)**:身份提供者的服务器,负责发放令牌。 **资源服务器(Resource Server)**:存储用户资源的服务器,需要令牌才能访问。 OAuth 2.0 定义了几种授权流程(Grant Types),每种适用于不同的场景。对于移动应用和跨平台应用,**授权码流程(Authorization Code Flow)** 是最安全的选择。 ### 🔄 13.2.2 授权码流程详解 授权码流程是目前最安全的 OAuth 2.0 流程,它通过一个两步验证过程来确保令牌的安全。 **第一步:获取授权码**。当用户点击"登录"时,应用会打开系统浏览器,导航到身份提供者的授权端点。URL 中包含以下参数: ```plaintext https://auth.example.com/authorize? response_type=code& client_id=my-app-client-id& redirect_uri=myapp://callback& scope=openid profile email& state=random_state_string& code_challenge=hashed_code_verifier& code_challenge_method=S256 ``` > **技术术语**:**PKCE(Proof Key for Code Exchange)** 是授权码流程的一个安全扩展。它通过 `code_verifier` 和 `code_challenge` 防止授权码被恶意应用截获。在移动应用中,PKCE 是强制要求的,因为自定义 URL Scheme 可能被其他应用注册。 **第二步:用户认证和授权**。用户在浏览器中输入用户名和密码,并授权应用访问其信息。身份提供者验证用户身份后,会将浏览器重定向回应用: ```plaintext myapp://callback?code=authorization_code&state=random_state_string ``` **第三步:用授权码换取令牌**。应用捕获这个回调,提取授权码,然后向身份提供者的令牌端点发送一个后端请求: ```csharp POST /token HTTP/1.1 Host: auth.example.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code& code=authorization_code& redirect_uri=myapp://callback& client_id=my-app-client-id& code_verifier=original_code_verifier ``` 身份提供者验证请求后,返回访问令牌和刷新令牌: ```json { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...", "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "scope": "openid profile email" } ``` ### 🆔 13.2.3 OpenID Connect:身份层 OAuth 2.0 只是一个授权框架,它不关心用户是谁。OpenID Connect (OIDC) 在 OAuth 2.0 之上添加了一个身份层,提供了用户身份信息。 OIDC 引入了 **ID Token** 的概念,这是一个 JWT(JSON Web Token),包含了用户的身份信息。通过解码 ID Token,应用可以获取用户的唯一标识符、邮箱、姓名等信息,而无需额外请求。 ```json // ID Token 解码后的内容示例 { "sub": "user-unique-id-12345", "name": "张三", "email": "zhangsan@example.com", "email_verified": true, "picture": "https://example.com/avatar.jpg", "iat": 1634567890, "exp": 1634571490 } ``` > **费曼技巧提问**:为什么授权码流程需要两步(先获取授权码,再换取令牌),而不是一步到位? > > 想象你要从银行取钱。如果只经过柜台人员,你告诉他们你的密码,他们就直接给你现金——这样做的问题是,柜台人员知道了你的密码。更安全的方式是:你先在一个私密房间里验证身份,得到一张"取款凭证"(授权码),然后用这张凭证在柜台取钱(换取令牌)。这样,柜台人员永远不知道你的密码,只知道你有合法的取款凭证。授权码流程就是这样设计的:浏览器和用户可见的部分只传递授权码,真正的令牌只在后端通道中传递。 --- ## 🛠️ 13.3 使用 Uno.Extensions.Authentication 简化认证 Uno Platform 提供了 `Uno.Extensions.Authentication` 库,它封装了复杂的 OAuth 2.0 / OIDC 流程,让你只需几行配置就能实现完整的认证系统。 ### ⚙️ 13.3.1 安装依赖 首先,在你的 Shared 项目中添加 NuGet 包: ```xml <ItemGroup> <PackageReference Include="Uno.Extensions.Authentication" Version="*" /> <PackageReference Include="Uno.Extensions.Authentication.Oidc" Version="*" /> <PackageReference Include="Uno.Extensions.Hosting" Version="*" /> </ItemGroup> ``` ### 🔧 13.3.2 配置认证服务 在 `App.xaml.cs` 中配置认证服务。Uno.Extensions 使用了现代化的主机(Host)模式,让依赖注入和配置管理变得简单。 ```csharp // 文件位置:App.xaml.cs using Microsoft.UI.Xaml; using Uno.Extensions.Hosting; using Uno.Extensions.Authentication; using Uno.Extensions.Navigation; public sealed partial class App : Application { protected async override void OnLaunched(LaunchActivatedEventArgs args) { // 创建应用主机构建器 var builder = this.CreateBuilder(args); // 配置服务 builder // 添加导航支持 .UseNavigation<App>() // 配置认证服务 .UseAuthentication(auth => { // 配置 OIDC 认证提供者 auth.AddOidc(oidc => { // 身份提供者的基础地址 // 这里使用 Duende Software 的演示服务器作为示例 // 在生产环境中,替换为你自己的身份提供者地址 oidc.Authority = "https://demo.duendesoftware.com"; // 客户端 ID // 在身份提供者处注册应用时获得 oidc.ClientId = "interactive.confidential"; // 客户端密钥(对于机密客户端) // 注意:在移动应用中,客户端密钥不能真正保密 // 应该使用 PKCE 来增强安全性 oidc.ClientSecret = "secret"; // 请求的权限范围 // openid: 获取用户身份 // profile: 获取用户基本信息 // email: 获取用户邮箱 // offline_access: 获取刷新令牌 oidc.Scopes = new[] { "openid", "profile", "email", "offline_access" }; // 重定向 URI // 这是用户登录成功后返回应用的地址 // 必须与身份提供者处注册的 URI 完全匹配 oidc.RedirectUri = "myapp://callback"; // 登出后的重定向 URI oidc.PostLogoutRedirectUri = "myapp://logout"; // 自动刷新令牌 // 当访问令牌过期时,自动使用刷新令牌获取新的访问令牌 oidc.AutoRefreshToken = true; }); }) // 配置其他服务 .ConfigureServices(services => { // 注册你的服务 services.AddSingleton<IUserService, UserService>(); }); // 构建主机并启动应用 var window = builder.Window; var host = builder.Build(); // 激活窗口 window.Activate(); } } ``` ### 📱 13.3.3 处理认证回调 为了让应用能够接收认证回调,你需要配置平台特定的设置。 **Android 平台**:在 `Platforms/Android/AndroidManifest.xml` 中添加 Intent Filter: ```xml <activity android:name="crc64a0e0a82d0db9a07d.MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <!-- 自定义 URL Scheme --> <data android:scheme="myapp" android:host="callback" /> </intent-filter> </activity> ``` **iOS 平台**:在 `Platforms/iOS/Info.plist` 中添加 URL Type: ```xml <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>myapp</string> </array> </dict> </array> ``` ### 📝 13.3.4 在 ViewModel 中实现登录逻辑 ```csharp // 文件位置:ViewModels/LoginViewModel.cs using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Uno.Extensions.Authentication; using Uno.Extensions.Navigation; public partial class LoginViewModel : ObservableObject { private readonly IAuthenticationService _authService; private readonly INavigator _navigator; [ObservableProperty] private bool _isBusy; [ObservableProperty] private string _errorMessage; public LoginViewModel( IAuthenticationService authService, INavigator navigator) { _authService = authService; _navigator = navigator; } /// <summary> /// 登录命令 /// </summary> [RelayCommand] public async Task LoginAsync() { if (IsBusy) return; IsBusy = true; ErrorMessage = null; try { // 调用认证服务进行登录 // LoginAsync 会自动处理: // 1. 打开系统浏览器导航到登录页面 // 2. 等待用户完成登录 // 3. 拦截回调 URL 并提取授权码 // 4. 用授权码换取令牌 // 5. 安全存储令牌 var result = await _authService.LoginAsync(); if (result.Success) { // 登录成功,导航到主页 await _navigator.NavigateViewModelAsync<MainViewModel>(); } else { // 用户取消了登录,或登录失败 ErrorMessage = result.Error?.Message ?? "登录已取消"; } } catch (Exception ex) { ErrorMessage = $"登录失败: {ex.Message}"; } finally { IsBusy = false; } } /// <summary> /// 静默登录(尝试使用已存储的令牌) /// </summary> public async Task<bool> TrySilentLoginAsync() { // 检查是否有有效的令牌 var isAuthenticated = await _authService.IsAuthenticatedAsync(); if (isAuthenticated) { // 有有效的令牌,直接导航到主页 await _navigator.NavigateViewModelAsync<MainViewModel>(); return true; } // 尝试使用刷新令牌获取新的访问令牌 var refreshResult = await _authService.RefreshLoginAsync(); if (refreshResult.Success) { await _navigator.NavigateViewModelAsync<MainViewModel>(); return true; } return false; // 需要用户重新登录 } } ``` ### 🚪 13.3.5 登出实现 ```csharp // 文件位置:ViewModels/MainViewModel.cs public partial class MainViewModel : ObservableObject { private readonly IAuthenticationService _authService; private readonly INavigator _navigator; [ObservableProperty] private string _userName; [ObservableProperty] private string _userEmail; public MainViewModel( IAuthenticationService authService, INavigator navigator, IUserService userService) { _authService = authService; _navigator = navigator; // 获取当前用户信息 var user = userService.GetCurrentUser(); UserName = user?.Name ?? "未知用户"; UserEmail = user?.Email ?? ""; } /// <summary> /// 登出命令 /// </summary> [RelayCommand] public async Task LogoutAsync() { // 调用认证服务登出 // LogoutAsync 会: // 1. 清除本地存储的令牌 // 2. (可选)通知身份提供者撤销令牌 // 3. 清除用户会话 await _authService.LogoutAsync(); // 导航回登录页面 await _navigator.NavigateViewModelAsync<LoginViewModel>(); } } ``` --- ## 🏢 13.4 集成 Microsoft 认证(MSAL) 如果你的应用需要集成 Microsoft 365、Azure AD 或个人 Microsoft 账户,MSAL(Microsoft Authentication Library)是官方推荐的选择。 ### 📦 13.4.1 安装 MSAL 包 ```xml <ItemGroup> <PackageReference Include="Microsoft.Identity.Client" Version="4.*" /> </ItemGroup> ``` ### 🔧 13.4.2 配置 MSAL 应用 ```csharp // 文件位置:Services/MsalAuthService.cs using Microsoft.Identity.Client; public class MsalAuthService : IAuthService { private readonly IPublicClientApplication _pca; private readonly string[] _scopes = new[] { "User.Read" }; public MsalAuthService() { // 创建公共客户端应用 _pca = PublicClientApplicationBuilder .Create("your-client-id") // 在 Azure AD 中注册应用时获得 .WithAuthority(AzureCloudInstance.AzurePublic, "common") // 支持所有账户类型 .WithRedirectUri("msal{your-client-id}://auth") // MSAL 标准重定向 URI .Build(); } /// <summary> /// 交互式登录 /// </summary> public async Task<AuthResult> LoginAsync() { try { // 尝试静默获取令牌(如果已有缓存的令牌) var accounts = await _pca.GetAccountsAsync(); var firstAccount = accounts.FirstOrDefault(); if (firstAccount != null) { try { var silentResult = await _pca .AcquireTokenSilent(_scopes, firstAccount) .ExecuteAsync(); return new AuthResult { Success = true, AccessToken = silentResult.AccessToken, Account = silentResult.Account }; } catch (MsalUiRequiredException) { // 需要交互式登录 } } // 交互式登录 var interactiveResult = await _pca .AcquireTokenInteractive(_scopes) .WithUseEmbeddedWebView(false) // 使用系统浏览器 .ExecuteAsync(); return new AuthResult { Success = true, AccessToken = interactiveResult.AccessToken, Account = interactiveResult.Account }; } catch (MsalException ex) { return new AuthResult { Success = false, Error = ex.Message }; } } /// <summary> /// 获取访问令牌 /// </summary> public async Task<string> GetAccessTokenAsync() { try { var accounts = await _pca.GetAccountsAsync(); var firstAccount = accounts.FirstOrDefault(); if (firstAccount == null) { return null; // 用户未登录 } var result = await _pca .AcquireTokenSilent(_scopes, firstAccount) .ExecuteAsync(); return result.AccessToken; } catch (MsalUiRequiredException) { // 令牌过期或需要重新认证 return null; } } /// <summary> /// 登出 /// </summary> public async Task LogoutAsync() { var accounts = await _pca.GetAccountsAsync(); foreach (var account in accounts) { await _pca.RemoveAsync(account); } } } public class AuthResult { public bool Success { get; set; } public string AccessToken { get; set; } public IAccount Account { get; set; } public string Error { get; set; } } ``` --- ## 👆 13.5 生物识别认证:Face ID 与指纹 为了提升用户体验,许多应用会在首次登录后提示用户开启生物识别。这样,用户不需要每次输入密码,只需验证指纹或面部即可快速登录。 ### 🔬 13.5.1 使用 Windows Hello 风格 API Uno Platform 提供了对生物识别的跨平台抽象。以下是一个简化版的实现: ```csharp // 文件位置:Services/BiometricService.cs using Windows.Security.Credentials.UI; public class BiometricService { /// <summary> /// 检查设备是否支持生物识别 /// </summary> public async Task<bool> IsAvailableAsync() { try { // UserConsentVerifier 是 WinUI 的生物识别 API // 在不同平台上,它会调用: // - Windows: Windows Hello // - iOS: Face ID / Touch ID // - Android: Fingerprint / Face Unlock var availability = await UserConsentVerifier.CheckAvailabilityAsync(); return availability == UserConsentVerifierAvailability.Available; } catch { return false; } } /// <summary> /// 请求生物识别验证 /// </summary> /// <param name="message">提示消息</param> public async Task<bool> VerifyAsync(string message = "请验证身份") { try { var result = await UserConsentVerifier.RequestVerificationAsync(message); return result == UserConsentVerificationResult.Verified; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"生物识别验证失败: {ex.Message}"); return false; } } /// <summary> /// 检查并请求生物识别权限(iOS 特有) /// </summary> public async Task<bool> RequestPermissionAsync() { // 在 iOS 上,需要在 Info.plist 中添加权限说明 // <key>NSFaceIDUsageDescription</key> // <string>我们需要 Face ID 来验证您的身份</string> var isAvailable = await IsAvailableAsync(); return isAvailable; } } ``` ### 🔄 13.5.2 生物识别 + 令牌存储的登录流程 ```csharp // 文件位置:Services/QuickLoginService.cs public class QuickLoginService { private readonly BiometricService _biometric; private readonly SecureStorageService _secureStorage; private readonly IAuthenticationService _authService; public QuickLoginService( BiometricService biometric, SecureStorageService secureStorage, IAuthenticationService authService) { _biometric = biometric; _secureStorage = secureStorage; _authService = authService; } /// <summary> /// 尝试快速登录(使用生物识别) /// </summary> public async Task<QuickLoginResult> TryQuickLoginAsync() { // 1. 检查用户是否已登录 var isAuthenticated = await _authService.IsAuthenticatedAsync(); if (!isAuthenticated) { return QuickLoginResult.NeedFullLogin; } // 2. 检查是否启用了生物识别 var biometricEnabled = _secureStorage.GetCredential( "settings", "biometric_enabled")?.Password == "true"; if (!biometricEnabled) { return QuickLoginResult.NeedFullLogin; } // 3. 检查设备是否支持生物识别 var biometricAvailable = await _biometric.IsAvailableAsync(); if (!biometricAvailable) { return QuickLoginResult.NeedFullLogin; } // 4. 请求生物识别验证 var verified = await _biometric.VerifyAsync("验证身份以进入应用"); if (verified) { // 验证成功,使用存储的令牌刷新登录状态 var refreshResult = await _authService.RefreshLoginAsync(); if (refreshResult.Success) { return QuickLoginResult.Success; } } return QuickLoginResult.NeedFullLogin; } /// <summary> /// 启用生物识别快速登录 /// </summary> public async Task EnableBiometricAsync() { var available = await _biometric.IsAvailableAsync(); if (available) { // 验证一次生物识别以确认用户身份 var verified = await _biometric.VerifyAsync("验证身份以启用快速登录"); if (verified) { _secureStorage.SaveCredential("settings", "biometric_enabled", "true"); } } } } public enum QuickLoginResult { Success, NeedFullLogin, Failed } ``` --- ## ⏰ 13.6 处理会话过期与令牌刷新 Access Token 通常只有短期的有效期(如 1 小时)。当它过期时,应用需要使用 Refresh Token 获取新的 Access Token,而不需要用户重新登录。 ### 🔄 13.6.1 自动令牌刷新 ```csharp // 文件位置:Services/TokenRefreshHandler.cs using System.Net.Http.Headers; public class TokenRefreshHandler : DelegatingHandler { private readonly IAuthenticationService _authService; private readonly SemaphoreSlim _refreshLock = new SemaphoreSlim(1, 1); public TokenRefreshHandler(IAuthenticationService authService) { _authService = authService; } protected async override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { // 获取当前访问令牌 var accessToken = await _authService.GetAccessTokenAsync(); if (!string.IsNullOrEmpty(accessToken)) { request.Headers.Authorization = new AuthenticationHeaderValue( "Bearer", accessToken ); } // 发送请求 var response = await base.SendAsync(request, cancellationToken); // 如果返回 401,尝试刷新令牌并重试 if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) { var refreshSuccess = await RefreshTokenAsync(); if (refreshSuccess) { // 使用新令牌重试请求 var newAccessToken = await _authService.GetAccessTokenAsync(); request.Headers.Authorization = new AuthenticationHeaderValue( "Bearer", newAccessToken ); response = await base.SendAsync(request, cancellationToken); } else { // 刷新失败,触发登出 // 应用应该导航到登录页面 OnSessionExpired(); } } return response; } private async Task<bool> RefreshTokenAsync() { // 使用锁确保只刷新一次 await _refreshLock.WaitAsync(); try { var result = await _authService.RefreshLoginAsync(); return result.Success; } finally { _refreshLock.Release(); } } private void OnSessionExpired() { // 发布会话过期事件 // 在实际应用中,可以使用消息中心或事件聚合器 Messenger.Publish(new SessionExpiredMessage()); } } ``` ### 📡 13.6.2 在 HTTP 客户端中使用 ```csharp // 文件位置:Services/ApiService.cs public class ApiService { private readonly HttpClient _httpClient; public ApiService(IAuthenticationService authService, IHttpMessageHandlerFactory handlerFactory) { // 创建带有令牌刷新处理器的 HTTP 客户端 var handler = new TokenRefreshHandler(authService) { InnerHandler = new HttpClientHandler() }; _httpClient = new HttpClient(handler) { BaseAddress = new Uri("https://api.example.com/") }; } public async Task<UserProfile> GetProfileAsync() { var response = await _httpClient.GetAsync("user/profile"); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize<UserProfile>(json); } } ``` --- ## 📝 本章小结 身份验证和安全是企业级应用的基石。通过本章的学习,你已经掌握了在 Uno Platform 中构建安全认证系统的核心技能。 让我们回顾本章的关键要点: 第一,现代认证架构将身份验证委托给专业的身份提供者,通过 OAuth 2.0 和 OIDC 协议实现安全的令牌获取流程。授权码流程配合 PKCE 是移动应用的最佳实践。 第二,Uno.Extensions.Authentication 库极大地简化了认证实现。几行配置就能完成浏览器弹出、回调拦截、令牌交换和存储等复杂操作。 第三,对于 Microsoft 生态系统的应用,MSAL 提供了对 Azure AD 和 Microsoft 账户的原生支持,并能自动处理令牌缓存和刷新。 第四,生物识别认证提供了便捷的用户体验。通过 Windows Hello 风格的 API,你可以在所有平台上实现统一的生物识别验证。 第五,令牌刷新和会话管理是安全性的重要组成部分。自动刷新机制确保用户不会因为令牌过期而频繁重新登录,同时保持了安全性。 在下一章中,我们将进入性能优化的领域——学习如何让你的 Uno 应用运行得更快、更轻量。我们将探讨 AOT 编译、IL 裁剪、内存优化等技术。 --- > **动手实验**: > 1. **OAuth 演示应用**:使用 Duende Software 的演示服务器(https://demo.duendesoftware.com)创建一个简单的登录演示应用。实现登录、获取用户信息、登出功能。观察令牌的生命周期和自动刷新行为。 > 2. **生物识别开关**:创建一个设置页面,允许用户启用或禁用生物识别快速登录。使用之前学习的 SecureStorage 保存用户的偏好设置。 > 3. **API 调用封装**:实现一个完整的 API 服务类,包含自动令牌刷新、错误处理和重试逻辑。模拟一个需要认证的 API,测试令牌过期后的自动刷新行为。

讨论回复

0 条回复

还没有人回复,快来发表你的看法吧!