Sustainsys.Saml2 IDP发起流访问User.Claims跳转其他IDP问题
异常触发原因
- 核心配置错误:循环注册多SAML认证方案时,每次调用
services.AddAuthentication()都会覆盖全局认证默认配置,最终全局默认的Cookie认证方案为最后一个注册的SAML方案对应的Cookie。 - 身份匹配失败:IDP发起登录时,当前IDP认证成功后写入的是自身对应名称的Cookie,跳转至
GetTokenResponse接口时,全局默认Cookie方案与当前用户持有的Cookie名称不匹配,导致身份认证失败,触发全局挑战逻辑重定向至其他IDP。 - 环境差异原因:QA环境的IDP配置列表顺序与开发、生产环境不同,测试使用的IDP并非最后一个注册的方案,因此无法匹配全局默认Cookie;而开发、生产环境测试的IDP刚好是最后注册的方案,默认Cookie匹配成功,因此运行正常。
修复方案
- 修正认证方案注册逻辑
全局仅调用一次services.AddAuthentication()配置基础选项,禁止在循环内反复覆盖全局默认配置,参考修改代码:
// 移到循环外,全局仅调用一次 var authBuilder = services.AddAuthentication(); foreach (var Saml in samlProviderslist) { try { // 移除循环内的AddAuthentication调用,直接用authBuilder注册方案 authBuilder .AddSaml2(Saml.SchemeName, options => { // 原有Saml2配置保持不变 options.SPOptions = new Sustainsys.Saml2.Configuration.SPOptions() { AuthenticateRequestSigningBehavior = Sustainsys.Saml2.Configuration.SigningBehavior.Never, EntityId = new Sustainsys.Saml2.Metadata.EntityId(Saml.EntityId), ReturnUrl = new Uri(Saml.ReturnUrl), ModulePath = string.Format("/{0}", Saml.SchemeName) }; // 证书加载、IDP添加等原有逻辑保持不变 options.Notifications.AcsCommandResultCreated = AcsCommandResultCreated; options.IdentityProviders.Add( new Sustainsys.Saml2.IdentityProvider( new Sustainsys.Saml2.Metadata.EntityId(Saml.IdpEntityId), options.SPOptions) { MetadataLocation = Saml.IdpMetadata, AllowUnsolicitedAuthnResponse = true, LoadMetadata = true }); }) .AddCookie(Saml.SchemeName + "Cookies"); logger.LogInfo($"Auth Scheme Added : {Saml.SchemeName}"); } catch (Exception ex) { logger.LogError($"{ErrorCodes.GetErroCode("1002")} { Saml.SchemeName } .Exception :{ex.Message} "); } }
- 调整跳转逻辑传递当前Scheme参数
在AcsCommandResultCreated方法解析出当前Scheme后,将其作为参数追加到跳转URL中:
commandResult.Location = new Uri(jwtRedirectUri + tenantID.Rows[0][0] + $"&protocol=SAML&requestId={requestID}&scheme={schemeName}", UriKind.Relative);
- 修改
GetTokenResponse逻辑显式认证
不再依赖全局默认的User.Claims,通过传递的Scheme参数手动完成对应方案的身份认证,获取Claims:
// 新增scheme入参 public async Task<IActionResult> GetTokenResponse(string clientId, string requestId, string scheme, string protocol = "SAML") { _logger.LogInfo("Inside GetTokenResponse."); var attributeMappingList = await GetTokenAttributes(clientId, protocol); List<Claim> claimAttributes = new List<Claim>(); var failureRedirectUrl = await _dbConfigSettingRepo.Get(new { SettingName = $"SSO:SAML:FailureRedirectUrl", TenantId = clientId }); try { _logger.LogInfo("Inside GetTokenResponse inside try."); // 新增:显式调用对应Scheme的认证逻辑 var authResult = await HttpContext.AuthenticateAsync(scheme + "Cookies"); if (!authResult.Succeeded) { _logger.LogError(requestId, clientId, protocol, ErrorCodes.GetErroCode("1007")); return Redirect(failureRedirectUrl.SettingValue + "?errorcode=1007"); } // 用authResult.Principal.Claims替代User.Claims var userClaims = authResult.Principal.Claims; _logger.LogInfo($"Request Id: { requestId}. Claims in SAML response are:\n" + String.Join(",\n", userClaims.Select(o => o.Type + " : " + o.Value))); // 后续所有使用User.Claims的位置全部替换为userClaims即可,原有业务逻辑保持不变 if (!attributeMappingList.Any()) { _logger.LogError(requestId, clientId, protocol, ErrorCodes.GetErroCode("1004")); return Redirect(failureRedirectUrl.SettingValue + "?errorcode=1004"); } // 剩余原有逻辑保持不变 } catch (Exception ex) { _logger.LogError(requestId, clientId, protocol, ErrorCodes.GetErroCode("1007"), ex); return Redirect(failureRedirectUrl.SettingValue + "?errorcode=1007"); } }
内容的提问来源于stack exchange,提问作者Deepali D
相关产品推荐
相关产品推荐

