.NET Core应用同时集成JWT与SAML2(Azure SSO)的无冲突实现咨询
在.NET Core应用中同时使用JWT Bearer和SAML2 Azure SSO
以下是实现两种认证方式共存且无冲突的步骤:
1. 安装SAML2依赖包
首先安装用于SAML2认证的NuGet包,推荐使用ITfoxtec.Identity.Saml2.AspNetCore:
Install-Package ITfoxtec.Identity.Saml2.AspNetCore
2. 配置SAML2参数
在appsettings.json中添加Azure AD SAML2的配置信息:
"Saml2": { "IdPMetadata": "https://login.microsoftonline.com/{你的租户ID}/federationmetadata/2007-06/federationmetadata.xml?appid={你的应用ID}", "Issuer": "{你的应用发行者URI(需与Azure AD中配置一致)}", "CallbackPath": "/saml2/acs", "LogoutPath": "/saml2/logout", "SigningCertificatePath": "{可选:签名证书路径}", "SigningCertificatePassword": "{可选:证书密码}" }
3. 更新认证服务配置
修改现有AddAuthentication代码,添加SAML2认证配置,并明确默认认证方案以避免冲突:
// 读取JWT配置 var jwtTokenSetting = Configuration.GetSection("JwtTokenSetting").Get<JwtTokenConfig>(); services.AddSingleton(jwtTokenSetting); // 配置认证服务 services.AddAuthentication(options => { // 交互式请求(如页面访问)默认使用Cookie认证 options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) // 保留原有JWT Bearer配置 .AddJwtBearer(x => { x.RequireHttpsMetadata = true; x.SaveToken = true; x.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = jwtTokenSetting.Issuer, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtTokenSetting.Secret)), ValidAudience = jwtTokenSetting.Audience, ValidateAudience = true, ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(1), RequireSignedTokens = true }; }) // 添加SAML2认证配置 .AddSaml2(options => { Configuration.GetSection("Saml2").Bind(options); // 可选:启用认证请求签名(需配置签名证书) if (!string.IsNullOrEmpty(Configuration["Saml2:SigningCertificatePath"])) { options.SignAuthnRequest = true; options.SigningCertificate = new X509Certificate2( Configuration["Saml2:SigningCertificatePath"], Configuration["Saml2:SigningCertificatePassword"]); } // 根据安全需求调整证书验证模式 options.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.ChainTrust; });
4. 注册SAML2中间件
由于你的应用禁用了端点路由,需在Configure方法中添加SAML2中间件:
// 在UseMvc之前添加 app.UseSaml2(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });
5. 添加SSO登录入口到登录页面
修改Identity/Account/Login.cshtml,添加"通过SSO登录"按钮:
<div class="form-group mt-3"> <button type="submit" asp-action="SsoLogin" class="btn btn-secondary w-100">通过SSO登录</button> </div>
6. 实现SSO登录回调逻辑
在AccountController中添加处理SAML2登录的动作方法:
private readonly SignInManager<ApplicationUser> _signInManager; private readonly UserManager<ApplicationUser> _userManager; private readonly JwtTokenConfig _jwtTokenConfig; public AccountController(SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager, JwtTokenConfig jwtTokenConfig) { _signInManager = signInManager; _userManager = userManager; _jwtTokenConfig = jwtTokenConfig; } // 触发SAML2认证挑战 public IActionResult SsoLogin() { var redirectUrl = Url.Action("SsoLoginCallback", "Account"); var properties = _signInManager.ConfigureExternalAuthenticationProperties("Saml2", redirectUrl); return Challenge(properties, "Saml2"); } // 处理SAML2认证回调 public async Task<IActionResult> SsoLoginCallback() { var info = await _signInManager.GetExternalLoginInfoAsync(); if (info == null) { return RedirectToAction("Login"); } // 尝试使用外部登录信息登录本地账户 var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true); if (result.Succeeded) { // 可选:为登录用户生成JWT令牌(供API使用) var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey); var jwtToken = GenerateJwtToken(user); Response.Cookies.Append("JwtToken", jwtToken, new CookieOptions { HttpOnly = true, Secure = true }); return RedirectToAction("Index", "Home"); } else { // 创建新的本地账户(如果用户不存在) var email = info.Principal.FindFirstValue(ClaimTypes.Email); var user = new ApplicationUser { UserName = email, Email = email }; var createResult = await _userManager.CreateAsync(user); if (createResult.Succeeded) { createResult = await _userManager.AddLoginAsync(user, info); if (createResult.Succeeded) { await _signInManager.SignInAsync(user, isPersistent: false); var jwtToken = GenerateJwtToken(user); Response.Cookies.Append("JwtToken", jwtToken, new CookieOptions { HttpOnly = true, Secure = true }); return RedirectToAction("Index", "Home"); } } // 处理创建账户失败的情况 foreach (var error in createResult.Errors) { ModelState.AddModelError(string.Empty, error.Description); } return RedirectToAction("Login"); } } // 复用原有JWT令牌生成逻辑 private string GenerateJwtToken(ApplicationUser user) { var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Email, user.Email) }; var key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_jwtTokenConfig.Secret)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _jwtTokenConfig.Issuer, audience: _jwtTokenConfig.Audience, claims: claims, expires: DateTime.Now.AddMinutes(_jwtTokenConfig.ExpiryMinutes), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); }
7. 避免认证冲突的关键配置
- API端点指定JWT方案:对于API控制器,明确指定使用JWT Bearer认证:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ApiController] [Route("api/[controller]")] public class ApiController : ControllerBase { // API逻辑 } - 交互式页面使用Cookie方案:Razor Pages和MVC视图默认使用Cookie认证,无需额外配置,因为我们已将
DefaultAuthenticateScheme设为Cookie。 - Azure AD配置:确保Azure AD应用注册中的回复URL(Reply URL)设置为
https://{你的域名}/saml2/acs,并启用SAML2单点登录。
内容的提问来源于stack exchange,提问作者nani
相关产品推荐
相关产品推荐

