基于ITfoxtec SAML 2.0与ASP.NET Core MVC的IDP登录跳转实现问询
问题:基于ITfoxtec Identity SAML 2.0与ASP.NET Core MVC构建IDP时的登录页面跳转处理
我正在用ITfoxtec Identity SAML 2.0和ASP.NET Core MVC搭建基于Cookie认证的SAML SSO身份提供商(IDP)。需求是当服务提供商(SP)发送SAML认证请求时,IDP自动跳转到登录页面,验证用户凭据后再把用户重定向回SP并返回SAML响应。想请教怎么在GUI中处理用户登录逻辑,实现跳转至登录页面的功能?
以下是我当前的SAML登录方法代码:
public async Task<IActionResult> Login() { bool isSamlRequest = HttpContext.Request.Query.ContainsKey("SAMLRequest") || HttpContext.Request.Headers.ContainsKey("SAMLRequest"); if(!isSamlRequest) return RedirectToAction(actionName:"login",controllerName: "Account"); var requestBinding = new Saml2RedirectBinding(); var relyingParty = await ValidateRelyingParty(ReadRelyingPartyFromLoginRequest(requestBinding)); var saml2AuthnRequest = new Saml2AuthnRequest(GetRpSaml2Configuration(relyingParty)); try { requestBinding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnRequest); // **** Handle user login e.g. in GUI **** // Test user with session index and claims var sessionIndex = Guid.NewGuid().ToString(); var claims = CreateTestUserClaims(saml2AuthnRequest.Subject?.NameID?.ID); return LoginResponse(saml2AuthnRequest.Id, Saml2StatusCodes.Success, requestBinding.RelayState, relyingParty, sessionIndex, claims); } catch (Exception exc) { return LoginResponse(saml2AuthnRequest.Id, Saml2StatusCodes.Responder, requestBinding.RelayState, relyingParty); } }
解决方案
核心思路:收到SP的SAML请求后,先保存请求上下文到会话,跳转至登录页面;用户登录验证通过后,从会话中取出上下文生成SAML响应返回给SP。
1. 修改SAML登录方法,保存请求上下文并跳转登录页
替换原代码中直接生成测试用户的逻辑,改为保存请求信息后跳转登录页面:
public async Task<IActionResult> Login() { bool isSamlRequest = HttpContext.Request.Query.ContainsKey("SAMLRequest") || HttpContext.Request.Headers.ContainsKey("SAMLRequest"); if (!isSamlRequest) return RedirectToAction("Login", "Account"); var requestBinding = new Saml2RedirectBinding(); var relyingParty = await ValidateRelyingParty(ReadRelyingPartyFromLoginRequest(requestBinding)); var saml2AuthnRequest = new Saml2AuthnRequest(GetRpSaml2Configuration(relyingParty)); try { requestBinding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnRequest); // 将SAML请求关键信息存入会话,用于登录完成后生成响应 HttpContext.Session.SetString("SamlAuthnRequestId", saml2AuthnRequest.Id.ToString()); HttpContext.Session.SetString("RelayState", requestBinding.RelayState); // 假设你的RelyingParty有唯一标识Id,保存到会话以便后续获取 HttpContext.Session.SetString("RelyingPartyId", relyingParty.Id); // 跳转到Account控制器的登录页面 return RedirectToAction("Login", "Account"); } catch (Exception exc) { return LoginResponse(saml2AuthnRequest.Id, Saml2StatusCodes.Responder, requestBinding.RelayState, relyingParty); } }
2. 实现Account控制器的登录逻辑
在AccountController中处理登录页面展示和表单提交:
// 展示登录页面 public IActionResult Login(string returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; return View(); } // 处理登录表单提交 [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) { if (!ModelState.IsValid) { return View(model); } // 替换为你的实际用户凭据验证逻辑(比如用Identity框架或自定义验证) var user = await _userManager.FindByNameAsync(model.UserName); if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password)) { ModelState.AddModelError(string.Empty, "无效的登录凭据"); return View(model); } // 生成用户Claims var claims = await _userManager.GetClaimsAsync(user); var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties { IsPersistent = model.RememberMe }; // 完成Cookie认证 await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); // 检查会话中是否有SAML请求上下文 var samlAuthnRequestId = HttpContext.Session.GetString("SamlAuthnRequestId"); var relayState = HttpContext.Session.GetString("RelayState"); var relyingPartyId = HttpContext.Session.GetString("RelyingPartyId"); if (!string.IsNullOrEmpty(samlAuthnRequestId) && !string.IsNullOrEmpty(relyingPartyId)) { // 清理会话中的临时数据 HttpContext.Session.Remove("SamlAuthnRequestId"); HttpContext.Session.Remove("RelayState"); HttpContext.Session.Remove("RelyingPartyId"); // 根据Id获取对应的RelyingParty配置 var relyingParty = await GetRelyingPartyById(relyingPartyId); var sessionIndex = Guid.NewGuid().ToString(); // 返回SAML响应到SP return LoginResponse(new Saml2Id(samlAuthnRequestId), Saml2StatusCodes.Success, relayState, relyingParty, sessionIndex, claims); } // 无SAML请求时,跳转到默认页面 return RedirectToLocal(returnUrl); } // 辅助方法:安全跳转本地页面 private IActionResult RedirectToLocal(string returnUrl) { if (Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } return RedirectToAction("Index", "Home"); }
3. 确保项目启用会话
在Program.cs中添加会话配置(需放在认证中间件之前):
// 注册会话服务 builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); // 设置会话超时时间 options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); // ... // 启用会话中间件 app.UseSession(); app.UseAuthentication(); app.UseAuthorization();
内容的提问来源于stack exchange,提问作者Hjal Abderrazak
相关产品推荐
相关产品推荐

