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

使用Duende Identity Server 4时外部登录信息未存入IdentityProviders表问题

解决Duende IdentityServer4外部登录时IdentityProviders表无数据的问题

首先明确:AspNetUserLogins是ASP.NET Identity默认存储外部登录与本地用户关联关系的表,Duende IdentityServer4和ASP.NET Identity都不会自动往你自定义的IdentityProviders表写入数据,需要手动在外部登录回调逻辑中添加写入代码。以下是具体解决步骤:

1. 确认自定义表的实体与DbContext配置

先确保你的IdentityProvider实体类和数据库上下文配置正确:

// 示例IdentityProvider实体类
public class IdentityProvider
{
    public int Id { get; set; }
    public string ProviderName { get; set; } // 存储"Facebook"/"Google"等提供商名称
    public string ProviderUserId { get; set; } // 外部登录的用户ID
    public string UserId { get; set; } // 关联本地AspNetUsers的ID
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

    // 关联本地用户实体
    public virtual ApplicationUser User { get; set; }
}

在你的ApplicationDbContext中注册该实体:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    // 添加自定义表的DbSet
    public DbSet<IdentityProvider> IdentityProviders { get; set; }
}

执行数据库迁移并更新数据库,确保IdentityProviders表已正确创建。

2. 修改外部登录回调逻辑,写入自定义表

找到项目中处理外部登录回调的代码(MVC项目是AccountController的ExternalLoginCallback方法,Razor Pages项目是ExternalLogin.cshtml.cs的OnGetCallbackAsync方法),在用户登录/关联成功的分支中添加写入IdentityProviders表的逻辑:

MVC示例代码

public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
    // 原有获取外部登录信息的逻辑
    var info = await _signInManager.GetExternalLoginInfoAsync();
    if (info == null)
    {
        return RedirectToAction(nameof(Login));
    }

    // 尝试直接登录已关联的用户
    var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
    if (result.Succeeded)
    {
        var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
        if (user != null)
        {
            // 检查是否已存在记录,避免重复写入
            var exists = await _context.IdentityProviders
                .AnyAsync(ip => ip.ProviderName == info.LoginProvider && ip.UserId == user.Id);
            if (!exists)
            {
                _context.IdentityProviders.Add(new IdentityProvider
                {
                    ProviderName = info.LoginProvider,
                    ProviderUserId = info.ProviderKey,
                    UserId = user.Id
                });
                await _context.SaveChangesAsync();
            }
        }
        return RedirectToLocal(returnUrl);
    }

    // 处理新用户注册并关联外部登录的逻辑
    var email = info.Principal.FindFirstValue(ClaimTypes.Email);
    if (email != null)
    {
        var user = new ApplicationUser { UserName = email, Email = email };
        var createResult = await _userManager.CreateAsync(user);
        if (createResult.Succeeded)
        {
            var addLoginResult = await _userManager.AddLoginAsync(user, info);
            if (addLoginResult.Succeeded)
            {
                // 写入自定义表
                _context.IdentityProviders.Add(new IdentityProvider
                {
                    ProviderName = info.LoginProvider,
                    ProviderUserId = info.ProviderKey,
                    UserId = user.Id
                });
                await _context.SaveChangesAsync();

                await _signInManager.SignInAsync(user, isPersistent: false);
                return RedirectToLocal(returnUrl);
            }
        }
        foreach (var error in createResult.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    return View("ExternalLogin", new ExternalLoginViewModel { ReturnUrl = returnUrl });
}

Razor Pages示例(ExternalLogin.cshtml.cs)

在OnGetCallbackAsync方法的对应成功分支添加类似逻辑:

public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
    // 原有逻辑...
    
    if (result.Succeeded)
    {
        var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
        if (user != null)
        {
            var exists = await _context.IdentityProviders
                .AnyAsync(ip => ip.ProviderName == info.LoginProvider && ip.UserId == user.Id);
            if (!exists)
            {
                _context.IdentityProviders.Add(new IdentityProvider
                {
                    ProviderName = info.LoginProvider,
                    ProviderUserId = info.ProviderKey,
                    UserId = user.Id
                });
                await _context.SaveChangesAsync();
            }
        }
        return LocalRedirect(returnUrl);
    }

    // 新用户注册后的写入逻辑...
}

3. 关键注意事项

  • 确保ApplicationDbContext已正确注入到控制器或PageModel中;
  • 必须添加重复检查逻辑,避免同一用户同一提供商的记录重复写入;
  • 如果使用了自定义的Identity UI,需找到对应的回调处理代码进行修改。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 21:27:21