ASP.Net Core处理ADFS响应时GetExternalLoginInfoAsync返回null问题
我之前也碰到过一模一样的问题!当ADFS回调到my-site/signin-federation时,_signInManager.GetExternalLoginInfoAsync()始终返回null,大概率是WSFederation中间件的配置逻辑或者回调路径的处理方式出了问题,咱们一步步排查解决:
这是最容易踩的坑:很多人会自己写Controller Action来处理/signin-federation的POST请求,但实际上WSFederation中间件已经自动注册了这个路径的处理逻辑,手动写Action会干扰中间件解析wresult、wctx等参数,导致GetExternalLoginInfoAsync()拿不到会话数据。
正确的配置应该是在Program.cs(或旧版的Startup.cs)里让中间件直接管理回调路径:
services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme; }) .AddCookie() .AddWsFederation(options => { options.MetadataAddress = "https://你的ADFS地址/FederationMetadata/2007-06/FederationMetadata.xml"; options.Wtrealm = "你的应用Realm URI"; // 和ADFS里配置的信赖方一致 options.CallbackPath = "/signin-federation"; // 必须和你的回调地址完全匹配 });
同时,不要自己写针对/signin-federation的POST Action,让中间件自动处理这个请求。
确保你发起ADFS登录时,是通过ASP.NET Core的认证挑战机制生成的跳转,而不是手动构造URL:
// 登录按钮对应的Action public IActionResult Login() { // 用ChallengeAsync触发WSFederation登录,自动生成正确的wctx参数 return Challenge(new AuthenticationProperties { RedirectUri = "/" }, WsFederationDefaults.AuthenticationScheme); }
wctx参数里包含了ASP.NET Core需要的会话状态信息,手动构造URL很容易丢失这个参数,导致回调时无法关联外部登录会话。
如果你确实需要在自定义Action里处理回调(比如加额外的业务逻辑),可以先手动读取表单并解析wresult,再构造ExternalLoginInfo:
[HttpPost("/signin-federation")] public async Task<IActionResult> CustomFederationCallback() { var form = await Request.ReadFormAsync(); var wresult = form["wresult"]; var wctx = form["wctx"]; if (string.IsNullOrEmpty(wresult)) { return BadRequest("缺少wresult参数"); } // 手动解析WSFederation令牌 var tokenHandler = new WSFederationSecurityTokenHandler(); var securityToken = tokenHandler.ReadToken(wresult); // 验证令牌(参数要和ADFS配置一致) var validationParams = new TokenValidationParameters { ValidAudience = "你的应用Realm URI", ValidIssuer = "https://你的ADFS地址/adfs/services/trust", ValidateLifetime = true }; var claimsPrincipal = tokenHandler.ValidateToken(securityToken, validationParams, out _); // 构造ExternalLoginInfo供SignInManager使用 var loginInfo = new ExternalLoginInfo( HttpContext, WsFederationDefaults.AuthenticationScheme, // 取ADFS返回的唯一标识Claim,比如NameIdentifier claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value, claimsPrincipal.FindFirst(ClaimTypes.Name)?.Value) { Principal = claimsPrincipal }; // 执行外部登录逻辑 var result = await _signInManager.ExternalLoginSignInAsync( loginInfo.LoginProvider, loginInfo.ProviderKey, isPersistent: false, bypassTwoFactor: true); if (result.Succeeded) { return RedirectToAction("Index", "Home"); } // 处理登录失败的情况 return RedirectToAction("Login"); }
不过这种方式需要自己处理令牌验证和会话关联,不如让中间件自动处理可靠,优先推荐第一种方案。
确保在Program.cs里,认证中间件的注册顺序正确:
app.UseRouting(); app.UseAuthentication(); // 必须在UseAuthorization之前 app.UseAuthorization();
内容的提问来源于stack exchange,提问作者Quentin

