Angular+IdentityServer4实现IdP-initiated SSO遇重定向及令牌问题求助
IdP-initiated SSO登录问题(Angular + IdentityServer4 + Sustainsys.Saml2)
我在Angular应用(运行于http://localhost:4200)和IdentityServer4环境(运行于http://localhost:7000)中,尝试使用Sustainsys.Saml2库实现IdP-initiated SSO登录,遇到以下问题。
IdentityServer4配置
services.AddAuthentication() .AddCookie() .AddSaml2("Saml2", "Login with SSO", opt => { opt.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; opt.SignOutScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; opt.SPOptions.EntityId = new EntityId("http://localhost:7000/Saml2/"); opt.SPOptions.ReturnUrl = new Uri("http://localhost:4200/idp-redirect?idp=Saml2"); // Idp using a custom query string param on the Acs URL. opt.IdentityProviders.Add(new IdentityProvider(new EntityId("https://stubidp.sustainsys.com/1b1f27d2-1d59-46aa-8756-3597337da1fe/Metadata"), opt.SPOptions) { LoadMetadata = true, AllowUnsolicitedAuthnResponse = true, }); opt.Notifications.AcsCommandResultCreated = AcsCommandResultCreated; }); private static void AcsCommandResultCreated(CommandResult commandResult, Saml2Response saml2Response) { var httpContext = _httpContextAccessor.HttpContext; var target = httpContext.Request.Query["target"].SingleOrDefault(); if (!string.IsNullOrEmpty(target)) { var targetUri = new Uri(target, UriKind.Relative); if (target.StartsWith("//")) { throw new InvalidOperationException("Protocol relative URLs are not allowed."); } commandResult.Location = targetUri; } }
ExternalController.cs代码
public async Task<IActionResult> Callback() { // read external identity from the temporary cookie var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); if (result?.Succeeded != true) { throw new Exception("External authentication error"); } if (_logger.IsEnabled(LogLevel.Debug)) { var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}"); _logger.LogDebug("External claims: {@claims}", externalClaims); } // lookup our user and external provider info var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result); if (user == null) { //getting user data from my database string email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; Member _member = await _memberDal.GetAsync(x => x.Email == email); if (_member == null) { user = null; } else { user = new TestUser { SubjectId = _member.Email, Username = _member.FirstName + " " + _member.LastName, }; } } // this allows us to collect any additional claims or properties // for the specific protocols used and store them in the local auth cookie. // this is typically used to store data needed for signout from those protocols. var additionalLocalClaims = new List<Claim>(); var localSignInProps = new AuthenticationProperties(); ProcessLoginCallback(result, additionalLocalClaims, localSignInProps); // issue authentication cookie for user var isuser = new IdentityServerUser(user.SubjectId) { DisplayName = user.Username, IdentityProvider = provider, AdditionalClaims = additionalLocalClaims }; await HttpContext.SignInAsync(isuser, localSignInProps); // delete temporary cookie used during external authentication await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); // retrieve return URL var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; // check if external login is in the context of an OIDC request var context = await _interaction.GetAuthorizationContextAsync(returnUrl); await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId)); if (context != null) { if (context.IsNativeClient()) { // The client is native, so this change in how to // return the response is for better UX for the end user. return this.LoadingPage("Redirect", returnUrl); } } return Redirect(returnUrl); }
我参考了Sustainsys.Saml2的IdentityServer集成相关示例。
问题描述
首次登录时Angular应用无法获取令牌,点击「我的账户」链接会再次重定向至IdentityServer验证登录状态,但IdentityServer已完成登录并成功重定向至Angular应用。我怀疑returnUrl生成有误,但不确定正确的生成方式。
更新:日志信息
2024-10-01 15:43:23 info: Sustainsys.Saml2.AspNetCore2.Saml2Handler[0] 2024-10-01 15:43:23 Successfully processed SAML response id752f7f4f578f4b788a86572bfa7d1f8d and authenticated JohnDoe 2024-10-01 15:43:25 info: IdentityServer4.Hosting.IdentityServerMiddleware[0] 2024-10-01 15:43:25 Invoking IdentityServer endpoint: IdentityServer4.Endpoints.AuthorizeEndpoint for /connect/authorize 2024-10-01 15:43:26 info: IdentityServer4.ResponseHandling.AuthorizeInteractionResponseGenerator[0] 2024-10-01 15:43:26 Showing login: Current IdP (local) is not the requested IdP (Saml2) 2024-10-01 15:43:39 info: Sustainsys.Saml2.AspNetCore2.Saml2Handler[0] 2024-10-01 15:43:39 Initiating login to https://stubidp.sustainsys.com/1b1f27d2-1d59-46aa-8756-3597337da1fe/Metadata 2024-10-01 15:43:47 fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1] 2024-10-01 15:43:47 An unhandled exception has occurred while executing the request. 2024-10-01 15:43:47 Sustainsys.Saml2.Exceptions.UnexpectedInResponseToException: Received message id393c68a4474d4692a2d0736ec17abc40 contains unexpected InResponseTo "id7a9f26358b48447a87db08d760e155a5". No cookie preserving state from the request was found so the message was not expected to have an InResponseTo attribute. This error typically occurs if the cookie set when doing SP-initiated sign on have been lost. 2024-10-01 15:43:47 at Sustainsys.Saml2.Saml2P.Saml2Response.ValidateInResponseTo(IOptions options, IEnumerable`1 claimsIdentities) 2024-10-01 15:43:47 at Sustainsys.Saml2.Saml2P.Saml2Response.GetClaims(IOptions options, IDictionary`2 relayData) 2024-10-01 15:43:47 at Sustainsys.Saml2.WebSso.AcsCommand.ProcessResponse(IOptions options, Saml2Response samlResponse, StoredRequestState storedRequestState, IdentityProvider identityProvider, String relayState) 2024-10-01 15:43:47 at Sustainsys.Saml2.WebSso.AcsCommand.Run(HttpRequestData request, IOptions options) 2024-10-01 15:43:47 at Sustainsys.Saml2.AspNetCore2.Saml2Handler.HandleRequestAsync() 2024-10-01 15:43:47 at IdentityServer4.Hosting.FederatedSignOut.AuthenticationRequestHandlerWrapper.HandleRequestAsync() 2024-10-01 15:43:47 at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) 2024-10-01 15:43:47 at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Cookie配置更新
我已更新Cookie配置:
services.Configure<CookiePolicyOptions>(options => { var httpContext = _httpContextAccessor.HttpContext; // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.OnAppendCookie = cookieContext => SameSiteCookieHelper.CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); options.OnDeleteCookie = cookieContext => SameSiteCookieHelper.CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); //this is important. this will help you to solve the problem. options.MinimumSameSitePolicy = SameSiteMode.Unspecified; });
SameSiteCookieHelper类代码:
public static class SameSiteCookieHelper { public static void CheckSameSite(HttpContext httpContext, CookieOptions options) { if (options.SameSite == SameSiteMode.None) { if (!BrowserSupportsSameSiteNone(httpContext.Request.Headers["User-Agent"].ToString())) { // Unspecified - no SameSite will be included in the Set-Cookie. options.SameSite = SameSiteMode.Unspecified; } } } private static bool BrowserSupportsSameSiteNone(string userAgent) { // iOS 12 browsers don't support SameSite.None. if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12")) { return false; } // macOS 10.14 Mojave browsers don't support SameSite.None. if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") && userAgent.Contains("Version/") && userAgent.Contains("Safari")) { return false; } // Old versions of Chrome don't support SameSite.None. if (userAgent.Contains("Chrome") || userAgent.Contains("Chrome/6")) { return false; } return true; } }
更新2:流程疑问
系统从stubidp(IdP)启动,重定向至Angular应用后,Angular生成认证请求URL并重定向至IdentityServer验证SAML响应与用户身份。我认为此时应重定向回Angular,但IdentityServer却再次重定向至stubidp,这是否符合IdP-initiated SSO的正确流程?
内容的提问来源于stack exchange,提问作者ypbr
相关产品推荐
相关产品推荐

