You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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即可。


注意事项

  1. 方案一需处理端口占用问题,可实现端口自动探测(尝试多个端口直到找到可用的)。
  2. 方案二注册注册表需要管理员权限,建议在安装程序中完成注册,或首次启动时提示用户以管理员身份运行。
  3. 令牌传输可额外加密,提升安全性;方案一的本地HTTP回调仅本机可访问,无需担心跨网泄露。

内容的提问来源于stack exchange,提问作者niks

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 16:07:07