.NET 7 WPF应用中如何处理AuthenticationState及用户信息存储?
.NET 7 WPF中存储与访问登录用户信息的方案
关于Thread.CurrentPrincipal的局限性
你当前使用的Thread.CurrentPrincipal和AppDomain.CurrentDomain.SetThreadPrincipal存在明显局限:
- 仅作用于当前线程,WPF的多线程模型(UI线程、后台线程)下,跨线程访问会出现不一致问题
- 应用重启后状态丢失,无法持久化登录状态
- 无内置状态变更通知机制,无法自动同步UI(比如登录后切换菜单权限)
推荐实现方案
WPF没有ASP.NET Core那样内置的AuthenticationStateProvider,通常需要自行实现全局认证状态管理服务,结合持久化存储来处理。以下是具体实现步骤:
1. 实现全局认证状态服务
创建一个单例或可注入的服务类,管理用户ClaimsPrincipal和JWT令牌,同时提供状态变更通知:
using System.ComponentModel; using System.Security.Claims; using System.IdentityModel.Tokens.Jwt; using System.Runtime.CompilerServices; public class AuthStateManager : INotifyPropertyChanged { private ClaimsPrincipal _currentUser = new ClaimsPrincipal(new ClaimsIdentity()); private string _jwtToken; public ClaimsPrincipal CurrentUser { get => _currentUser; set { _currentUser = value; OnPropertyChanged(); } } public string JwtToken { get => _jwtToken; set { _jwtToken = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } // 从JWT解析并加载用户声明 public void LoadUserFromToken(string token) { var tokenHandler = new JwtSecurityTokenHandler(); var jwtToken = tokenHandler.ReadJwtToken(token); var identity = new ClaimsIdentity(jwtToken.Claims, "Jwt"); CurrentUser = new ClaimsPrincipal(identity); JwtToken = token; } // 注销用户,清空状态 public void Logout() { CurrentUser = new ClaimsPrincipal(new ClaimsIdentity()); JwtToken = null; } }
在App.xaml.cs中注册为单例(或用依赖注入容器注册):
public partial class App : Application { public static AuthStateManager AuthState { get; } = new AuthStateManager(); // 若使用依赖注入,可替换为: // protected override void OnStartup(StartupEventArgs e) // { // var services = new ServiceCollection(); // services.AddSingleton<AuthStateManager>(); // // 其他服务注册... // base.OnStartup(e); // } }
2. 加密持久化JWT令牌
为实现重启后自动登录,需将JWT令牌加密存储到本地,推荐使用ProtectedData(系统级加密,需引用System.Security.Cryptography):
using System.Security.Cryptography; using System.Text; public static class TokenStorage { private const string TokenKey = "UserJwtToken"; // 应用专属熵值,增强加密安全性 private static readonly byte[] Entropy = Encoding.UTF8.GetBytes("YourAppUniqueEntropyKey"); public static void SaveToken(string token) { byte[] tokenBytes = Encoding.UTF8.GetBytes(token); byte[] encryptedBytes = ProtectedData.Protect(tokenBytes, Entropy, DataProtectionScope.CurrentUser); Properties.Settings.Default[TokenKey] = Convert.ToBase64String(encryptedBytes); Properties.Settings.Default.Save(); } public static string GetToken() { var encryptedBase64 = Properties.Settings.Default[TokenKey] as string; if (string.IsNullOrEmpty(encryptedBase64)) return null; byte[] encryptedBytes = Convert.FromBase64String(encryptedBase64); byte[] tokenBytes = ProtectedData.Unprotect(encryptedBytes, Entropy, DataProtectionScope.CurrentUser); return Encoding.UTF8.GetString(tokenBytes); } public static void ClearToken() { Properties.Settings.Default[TokenKey] = null; Properties.Settings.Default.Save(); } }
3. 整合到登录流程
修改你的LoginButton_Click方法,替换为全局状态管理+持久化逻辑:
private async void LoginButton_Click(object sender, RoutedEventArgs e) { var client = _httpClientFactory.CreateClient(); var tokenResponse = await client.PostAsJsonAsync("https://localhost:5001/api/login/pos", new LoginCredentials(UsernameTextBox.Text, PasswordBox.Password)); if (tokenResponse.IsSuccessStatusCode) { var loginResult = await tokenResponse.Content.ReadFromJsonAsync<LoginResult>(); var token = loginResult.Token; // 更新全局认证状态 App.AuthState.LoadUserFromToken(token); // 持久化令牌 TokenStorage.SaveToken(token); // 这里添加登录成功后的页面跳转逻辑 // new MainDashboard().Show(); // Close(); } else { MessageBox.Show(await tokenResponse.Content.ReadAsStringAsync(), "登录失败"); } }
4. 应用启动时自动恢复登录
在App.xaml.cs的OnStartup方法中添加自动登录逻辑:
protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var savedToken = TokenStorage.GetToken(); if (!string.IsNullOrEmpty(savedToken)) { var tokenHandler = new JwtSecurityTokenHandler(); if (tokenHandler.CanReadToken(savedToken)) { var jwtToken = tokenHandler.ReadJwtToken(savedToken); // 验证令牌是否未过期 if (jwtToken.ValidTo > DateTime.UtcNow) { App.AuthState.LoadUserFromToken(savedToken); // 直接跳转到主页面 // new MainDashboard().Show(); // return; } else { TokenStorage.ClearToken(); } } } // 显示登录页面 new MainWindow().Show(); }
5. UI绑定与状态同步
在XAML中直接绑定AuthState的属性,实现UI自动更新:
<Window.Resources> <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> </Window.Resources> <!-- 显示用户名 --> <TextBlock Text="{Binding CurrentUser.Identity.Name}" DataContext="{x:Static local:App.AuthState}" /> <!-- 仅在登录后显示注销按钮 --> <Button Content="注销" Click="LogoutButton_Click" Visibility="{Binding CurrentUser.Identity.IsAuthenticated, Converter={StaticResource BooleanToVisibilityConverter}}" />
注销按钮逻辑:
private void LogoutButton_Click(object sender, RoutedEventArgs e) { App.AuthState.Logout(); TokenStorage.ClearToken(); // 跳转到登录页面 // new MainWindow().Show(); // Close(); }
总结
WPF没有内置的认证状态管理机制,自行实现全局AuthStateManager服务+加密持久化的方案,可满足:
- 全局统一访问用户信息
- 跨线程状态一致性(可按需添加线程安全处理)
- 持久化登录状态
- 状态变更自动同步UI
内容的提问来源于stack exchange,提问作者jeb
相关产品推荐
相关产品推荐

