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

如何利用Azure AD令牌及自定义声明实现ASP.NET MVC对Web API的认证

方案可行性与实现指南

你的需求方案是完全可行的,但需要注意:Azure AD颁发的原生令牌无法直接修改(修改后签名会失效,API端的Azure AD认证会拒绝),因此需要通过以下两种思路实现自定义声明的传递,结合最佳实践给出具体方案:


核心思路澄清

Azure AD令牌由微软签名,任何修改都会导致验证失败,因此不能直接将自定义声明添加到该令牌中。需通过以下两种方案实现需求:


方案一:MVC应用签发自定义JWT(推荐)

逻辑流程

  1. 用户登录MVC应用获取Azure AD令牌
  2. MVC应用通过AD令牌中的用户唯一标识(如oid)从数据库拉取自定义声明
  3. MVC应用自行签发包含AD原有核心声明+自定义声明的新JWT令牌
  4. 使用该自定义JWT调用Web API,API端验证此JWT并获取自定义声明

代码实现

MVC端:签发自定义JWT

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;

public class TokenService
{
    private readonly IConfiguration _config;
    private readonly IUserClaimsRepository _claimsRepo;

    public TokenService(IConfiguration config, IUserClaimsRepository claimsRepo)
    {
        _config = config;
        _claimsRepo = claimsRepo;
    }

    public async Task<string> GenerateCustomApiToken(ClaimsPrincipal adUser)
    {
        // 获取Azure AD用户唯一标识(oid永久不变,比upn更可靠)
        var userId = adUser.FindFirstValue("oid");
        if (string.IsNullOrEmpty(userId))
            throw new InvalidOperationException("无法获取用户唯一标识");

        // 从数据库拉取自定义声明
        var customClaims = await _claimsRepo.GetUserClaimsAsync(userId);

        // 合并AD原有声明与自定义声明
        var allClaims = adUser.Claims.ToList();
        allClaims.AddRange(customClaims.Select(c => new Claim(c.ClaimType, c.ClaimValue)));

        // 配置JWT签名密钥与参数
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:SecretKey"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _config["Jwt:Issuer"], // MVC应用地址
            audience: _config["Jwt:ApiAudience"], // Web API的受众标识
            claims: allClaims,
            expires: DateTime.UtcNow.AddMinutes(30), // 短有效期降低泄露风险
            signingCredentials: creds
        );

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

Web API端:验证自定义JWT

在Program.cs中配置JWT认证:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:ApiAudience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]))
        };
    });

// 启用认证与授权中间件
app.UseAuthentication();
app.UseAuthorization();

方案二:两端通过IClaimsTransformation加载声明

逻辑流程

  1. 用户登录MVC应用后,MVC通过IClaimsTransformation从数据库加载自定义声明,附加到当前用户的ClaimsPrincipal
  2. MVC调用Web API时,仍使用Azure AD令牌
  3. Web API收到请求后,同样通过IClaimsTransformation,根据AD令牌中的用户标识从数据库加载自定义声明,附加到API端的ClaimsPrincipal

代码实现

通用IClaimsTransformation实现

public class CustomClaimsTransformer : IClaimsTransformation
{
    private readonly IUserClaimsRepository _claimsRepo;
    private readonly IMemoryCache _cache;

    public CustomClaimsTransformer(IUserClaimsRepository claimsRepo, IMemoryCache cache)
    {
        _claimsRepo = claimsRepo;
        _cache = cache;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        // 避免重复加载声明
        if (principal.HasClaim(c => c.Type == "CustomClaimType"))
            return principal;

        var userId = principal.FindFirstValue("oid");
        if (string.IsNullOrEmpty(userId))
            return principal;

        // 缓存自定义声明,减少数据库查询
        var cacheKey = $"UserClaims_{userId}";
        var customClaims = await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
            return await _claimsRepo.GetUserClaimsAsync(userId);
        });

        // 创建新身份,保留原有声明并添加自定义声明
        var identity = new ClaimsIdentity(principal.Identity);
        foreach (var claim in customClaims)
        {
            identity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue));
        }

        return new ClaimsPrincipal(identity);
    }
}

在MVC与API中注册服务

在两端的Program.cs中添加:

builder.Services.AddScoped<IClaimsTransformation, CustomClaimsTransformer>();
builder.Services.AddMemoryCache(); // 用于缓存声明

最佳实践建议

  1. 优先选择方案一:API无需直接访问数据库,降低系统耦合;可精确控制传递给API的声明范围,避免敏感信息泄露。
  2. 统一用户标识:始终使用Azure AD的oid(用户对象ID)作为数据库关联键,oid永久不变,比upn(用户主体名)更可靠。
  3. 缓存优化:无论哪种方案,都要缓存自定义声明,减少数据库查询次数,提升系统性能。
  4. 令牌安全:自定义JWT设置较短有效期(如30分钟),并实现刷新令牌机制;避免在令牌中存储敏感信息。
  5. API端可选双重验证:方案一中,API可额外验证原Azure AD令牌的有效性(通过Microsoft Graph的令牌验证端点),确保用户AD会话合法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 17:19:51