MVC Core 2+IdentityServer4中GetExternalLoginInfoAsync返回null问题求助
GetExternalLoginInfoAsync()返回null的问题 我之前碰到好几个开发者遇到过这个问题,核心原因其实是外部登录的临时信息只在回调请求的上下文里有效,过了那个请求周期就没了,咱们一步步拆解原因和解决方案:
为什么ExternalLoginCallback里能拿到,HomeController里不行?
IdentityServer4和ASP.NET Core Identity的外部登录流程是这样的:
- 当用户从IdentityServer重定向回客户端时,ASP.NET Core会把外部登录的临时数据(比如第三方提供商信息、用户凭证)存在**当前请求的临时存储(比如AuthenticationProperties或者Session)**里,这个数据是一次性的,只在
ExternalLoginCallback这个请求周期内可用。 GetExternalLoginInfoAsync()方法本质是从当前请求的上下文里读取这份临时数据,一旦你跳转到HomeController的Action,这个请求上下文已经切换,临时数据要么被清理,要么不在当前上下文里,自然返回null。
解决方案分两种场景
场景1:需要获取已登录用户的关联外部账号信息
如果用户已经完成了外部登录(比如你在ExternalLoginCallback里调用了ExternalLoginSignInAsync或者创建了本地用户并登录),此时用户已经有了本地身份,你不需要再用GetExternalLoginInfoAsync(),而是直接通过UserManager获取用户关联的外部登录:
// 在HomeController里注入UserManager private readonly UserManager<IdentityUser> _userManager; public HomeController(UserManager<IdentityUser> userManager) { _userManager = userManager; } public async Task<IActionResult> Index() { // 获取当前登录用户的所有外部登录关联信息 var externalLogins = await _userManager.GetLoginsAsync(User); foreach (var login in externalLogins) { // 可以拿到ProviderDisplayName、LoginProvider等信息 var providerName = login.LoginProvider; var providerKey = login.ProviderKey; } return View(); }
场景2:需要把登录时的临时外部信息带到HomeController
如果你想拿到登录时的临时数据(比如第三方用户的昵称、头像、邮箱这类),可以在ExternalLoginCallback里把这些信息存到TempData或者Session里,然后在HomeController里读取:
第一步:在ExternalLoginCallback里存储临时信息
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null) { var info = await _signInManager.GetExternalLoginInfoAsync(); if (info == null) { return RedirectToAction("Login", "Account"); } // 提取需要的临时信息,比如用户名、头像 var externalUserName = info.Principal.FindFirst(ClaimTypes.Name)?.Value; var externalAvatar = info.Principal.FindFirst("urn:identityserver:profile:avatar")?.Value; // 存到TempData(默认依赖Session,需要先启用Session) TempData["ExternalUserName"] = externalUserName; TempData["ExternalAvatar"] = externalAvatar; // 完成登录流程 var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true); if (result.Succeeded) { return RedirectToLocal(returnUrl); } // 其他逻辑(比如创建本地用户)... }
第二步:在HomeController里读取
public IActionResult Index() { var externalUserName = TempData["ExternalUserName"] as string; var externalAvatar = TempData["ExternalAvatar"] as string; // 用这些信息做展示或者其他处理 ViewBag.ExternalUserName = externalUserName; return View(); }
注意:使用TempData需要确保你的项目已经启用了Session,在Startup.cs里添加:
// ConfigureServices里 services.AddSession(); // Configure里(在UseMvc之前) app.UseSession();
场景3:检查是否在回调里完成了登录流程
如果你在ExternalLoginCallback里没有完成登录操作(比如跳过了ExternalLoginSignInAsync或者创建用户后未登录),那么用户在HomeController里处于未认证状态,GetExternalLoginInfoAsync也会返回null,因为它依赖当前请求的认证上下文临时数据。一定要确保在回调里完成登录流程,再跳转到HomeController。
总结
GetExternalLoginInfoAsync()是专门为外部登录的回调请求设计的,只能在回调的请求周期内使用。后续请求要获取用户的外部相关信息,要么通过UserManager查询已关联的账号,要么在回调时把临时数据存到Session/TempData里带到后续请求。
内容的提问来源于stack exchange,提问作者Saifal Maluk

