ASP.NET Core Identity结合WPF桌面应用的登录实现问询
实现WPF复用ASP.NET Core Identity认证的两种可行方案
方案一:本地HTTP监听+回调地址
核心思路
WPF启动本地HTTP监听服务,登录页携带该本地回调地址;登录成功后,ASP.NET后端生成JWT并重定向到该回调地址,WPF捕获请求后提取令牌。
WPF端代码实现
using System.Net; using System.Windows; using System.Diagnostics; public partial class MainWindow : Window { private HttpListener _listener; private readonly int _localPort = 5000; private string _callbackUrl => $"http://localhost:{_localPort}/callback"; public MainWindow() { InitializeComponent(); } private async void LoginBtn_Click(object sender, RoutedEventArgs e) { StartLocalListener(); // 携带回调地址打开登录页 var loginUrl = $"https://www.myapplogin.com/?callback={Uri.EscapeDataString(_callbackUrl)}"; Process.Start(new ProcessStartInfo { FileName = loginUrl, UseShellExecute = true }); } private void StartLocalListener() { _listener = new HttpListener(); _listener.Prefixes.Add($"{_callbackUrl}/"); _listener.Start(); _ = HandleCallbackRequests(); } private async Task HandleCallbackRequests() { try { while (_listener.IsListening) { var context = await _listener.GetContextAsync(); var token = context.Request.QueryString["token"]; if (!string.IsNullOrEmpty(token)) { // UI线程更新逻辑 Dispatcher.Invoke(() => { TokenStorage.SaveToken(token); MessageBox.Show("登录成功,令牌已获取"); }); // 返回浏览器关闭提示 var responseContent = "<html><body>登录完成,可关闭此窗口</body></html>"; var buffer = System.Text.Encoding.UTF8.GetBytes(responseContent); context.Response.ContentLength64 = buffer.Length; await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length); context.Response.OutputStream.Close(); // 停止监听 _listener.Stop(); _listener.Close(); break; } else { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; context.Response.Close(); } } } catch (Exception ex) { Dispatcher.Invoke(() => MessageBox.Show($"监听异常:{ex.Message}")); } } } // 令牌存储辅助类 public static class TokenStorage { public static string CurrentToken { get; private set; } public static void SaveToken(string token) { CurrentToken = token; // 可选:加密后保存到Windows凭据管理器或本地文件 } }
ASP.NET Core后端修改(登录成功逻辑)
using Microsoft.AspNetCore.Identity; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; [HttpPost] public async Task<IActionResult> Login(LoginViewModel model, string callback) { if (ModelState.IsValid) { var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { var user = await _userManager.FindByEmailAsync(model.Email); var jwtToken = GenerateJwtToken(user); // 处理回调重定向 if (!string.IsNullOrEmpty(callback) && Uri.IsWellFormedUriString(callback, UriKind.Absolute)) { var callbackUri = new UriBuilder(callback) { Query = $"token={Uri.EscapeDataString(jwtToken)}" }.Uri; return Redirect(callbackUri.ToString()); } return RedirectToAction("Index", "Home"); } ModelState.AddModelError("", "登录失败,请检查账号密码"); } return View(model); } // 生成JWT方法(复用现有实现即可) private string GenerateJwtToken(IdentityUser user) { var claims = new List<Claim> { new Claim(JwtRegisteredClaimNames.Sub, user.Email), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _configuration["Jwt:Issuer"], audience: _configuration["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddMinutes(30), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); }
方案二:自定义URI协议唤起WPF
核心思路
注册自定义URI协议(如myapp://),登录成功后后端重定向到该协议地址,系统自动唤起WPF应用,WPF从启动参数中提取令牌。
WPF端代码实现
1. 注册URI协议(App.xaml.cs)
using Microsoft.Win32; using System.Windows; using System.Web; public partial class App : Application { private const string ProtocolName = "myapp"; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); if (!IsProtocolRegistered()) RegisterProtocol(); HandleStartupArgs(e.Args); } private bool IsProtocolRegistered() { using (var key = Registry.ClassesRoot.OpenSubKey(ProtocolName)) { return key != null && key.GetValue("URL Protocol") != null; } } private void RegisterProtocol() { using (var rootKey = Registry.ClassesRoot.CreateSubKey(ProtocolName)) { rootKey.SetValue("", "URL:MyApp认证协议"); rootKey.SetValue("URL Protocol", ""); using (var cmdKey = rootKey.CreateSubKey(@"shell\open\command")) { var exePath = System.Reflection.Assembly.GetExecutingAssembly().Location; cmdKey.SetValue("", $"\"{exePath}\" \"%1\""); } } } private void HandleStartupArgs(string[] args) { if (args.Length == 0) return; if (Uri.TryCreate(args[0], UriKind.Absolute, out var uri) && uri.Scheme == ProtocolName) { var token = HttpUtility.ParseQueryString(uri.Query).Get("token"); if (!string.IsNullOrEmpty(token)) { TokenStorage.SaveToken(token); MessageBox.Show("登录成功,令牌已获取"); } } } }
2. 打开登录页代码
private void LoginBtn_Click(object sender, RoutedEventArgs e) { var loginUrl = "https://www.myapplogin.com/?callback=myapp://callback"; Process.Start(new ProcessStartInfo { FileName = loginUrl, UseShellExecute = true }); }
ASP.NET Core后端重定向逻辑
同方案一,仅回调地址替换为自定义URI即可。
注意事项
- 方案一需处理端口占用问题,可实现端口自动探测(尝试多个端口直到找到可用的)。
- 方案二注册注册表需要管理员权限,建议在安装程序中完成注册,或首次启动时提示用户以管理员身份运行。
- 令牌传输可额外加密,提升安全性;方案一的本地HTTP回调仅本机可访问,无需担心跨网泄露。
内容的提问来源于stack exchange,提问作者niks
相关产品推荐
相关产品推荐

