You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 22:21:38