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

ASP.NET Core 2.0整合Azure AD认证、自定义JWT及身份存储遇问题

我之前在整合这些模块的时候也踩过不少坑,结合单独示例的经验,给你梳理一套可行的整合方案,一步步来:

整合Azure AD认证与自定义JWT方案的实操步骤

1. 双认证方案配置:Azure AD做身份源,自定义JWT做默认认证

首先在Startup.cs里同时配置Azure AD和自定义JWT认证,核心是区分挑战方案(未认证时跳转到Azure AD登录)和默认认证方案(后续请求用自定义JWT验证):

services.AddAuthentication(options =>
{
    // 默认用自定义JWT处理已认证请求,实现跨实例一致性
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    // 未认证时触发Azure AD登录流程
    options.DefaultChallengeScheme = AzureADDefaults.AuthenticationScheme;
})
// 配置Azure AD身份提供者
.AddAzureAD(options => Configuration.Bind("AzureAd", options))
// 配置自定义JWT验证规则
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = Configuration["Jwt:Issuer"],
        ValidAudience = Configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"]))
    };
});

2. Azure AD登录成功后生成自定义JWT

在Azure AD登录回调的Action里,先获取Azure AD返回的用户身份,再结合自定义身份存储的角色、权限信息,生成我们自己的JWT令牌:

public async Task<IActionResult> AzureADCallback()
{
    // 验证Azure AD登录结果
    var aadAuthResult = await HttpContext.AuthenticateAsync(AzureADDefaults.AuthenticationScheme);
    if (!aadAuthResult.Succeeded)
    {
        return RedirectToAction("Login");
    }

    // 从自定义身份存储拉取用户角色、额外信息
    var userService = HttpContext.RequestServices.GetRequiredService<IUserService>();
    var userId = aadAuthResult.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
    var userRoles = await userService.GetUserRoles(userId);
    
    // 合并Azure AD的Claims和自定义Claims
    var customClaims = new List<Claim>();
    foreach (var role in userRoles)
    {
        customClaims.Add(new Claim(ClaimTypes.Role, role));
    }
    // 可添加其他自定义字段,比如用户昵称、权限码等
    customClaims.Add(new Claim("NickName", await userService.GetUserNickName(userId)));

    var claimsIdentity = new ClaimsIdentity(
        aadAuthResult.Principal.Claims.Concat(customClaims),
        JwtBearerDefaults.AuthenticationScheme
    );
    
    // 生成自定义JWT
    var jwtToken = GenerateJwtToken(claimsIdentity);

    // 保存令牌:可以写入Cookie供Web端使用,也可以直接返回给前端/桌面客户端
    Response.Cookies.Append("BearerToken", jwtToken, new CookieOptions 
    { 
        HttpOnly = true, 
        Secure = true, 
        SameSite = SameSiteMode.Strict,
        Expires = DateTime.Now.AddHours(8)
    });

    return RedirectToAction("Index", "Home");
}

// JWT生成工具方法
private string GenerateJwtToken(ClaimsIdentity identity)
{
    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"]));
    var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
        issuer: Configuration["Jwt:Issuer"],
        audience: Configuration["Jwt:Audience"],
        claims: identity.Claims,
        expires: DateTime.Now.AddHours(8),
        signingCredentials: credentials
    );

    return new JwtSecurityTokenHandler().WriteToken(token);
}

3. 支持桌面客户端使用Bearer令牌

桌面客户端只需要获取到这个JWT令牌(比如从Web应用的登录接口返回,或者用户手动复制),在请求API时,在请求头中添加:

Authorization: Bearer {你的JWT令牌}

因为我们已经配置了JwtBearer认证方案,API会自动验证令牌的有效性,无需额外修改。

4. 自定义角色与策略授权

基于自定义身份存储的角色,在Startup.cs中配置授权策略:

services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
    options.AddPolicy("EditorAccess", policy => policy.RequireRole("Editor", "Admin"));
    // 也可以基于自定义Claim配置策略,比如权限码
    options.AddPolicy("CanEditPosts", policy => policy.RequireClaim("Permission", "Post.Edit"));
});

然后在控制器或Action上直接使用:

[Authorize(Policy = "AdminOnly")]
public IActionResult AdminDashboard()
{
    return View();
}

5. 可选:自动Claims转换(更灵活的角色注入)

如果不想在登录回调里手动合并Claims,可以用IClaimsTransformation实现自动注入自定义角色:

// 注册Claims转换服务
services.AddScoped<IClaimsTransformation, CustomClaimsTransformation>();

public class CustomClaimsTransformation : IClaimsTransformation
{
    private readonly IUserService _userService;

    public CustomClaimsTransformation(IUserService userService)
    {
        _userService = userService;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        var userId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrEmpty(userId)) return principal;

        var roles = await _userService.GetUserRoles(userId);
        var claimsIdentity = principal.Identity as ClaimsIdentity;
        
        // 避免重复添加角色
        foreach (var role in roles)
        {
            if (!claimsIdentity.HasClaim(c => c.Type == ClaimTypes.Role && c.Value == role))
            {
                claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
            }
        }

        return principal;
    }
}

这样每次认证通过后,系统会自动从自定义身份存储拉取角色并注入到Claims中。

常见整合坑点排查

  • 认证方案冲突:务必确保DefaultAuthenticateScheme和DefaultChallengeScheme分别指向自定义JWT和Azure AD,否则会出现登录后仍未认证的问题。
  • JWT验证失败:检查配置文件中的Jwt:Issuer、Jwt:Audience、Jwt:SecretKey是否和生成令牌时一致,同时确认令牌未过期。
  • 角色不生效:确保自定义角色的Claim类型是ClaimTypes.Role,如果用了自定义类型(比如"Role"),需要在策略中用RequireClaim("Role", "Admin")指定。

内容的提问来源于stack exchange,提问作者Astasian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:04:30