使用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
相关产品推荐
相关产品推荐

