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

如何在C# Moq中模拟aud与appId声明及Azure AD认证测试

问题:如何用Moq模拟Azure AD的"aud"和"appId"声明?

我正在用Moq为基于Azure AD客户端凭据流实现的认证逻辑编写单元测试,首个测试用例是验证Audience是否有效,但尝试通过ClaimTypes设置"aud"或"appId"声明时,找不到类似ClaimTypes.Aud的预定义类型。现有代码如下:

var identity = new ClaimsIdentity(new Claim[] {
    new Claim(ClaimTypes.Name, "Sahil")
});
var mockPrincipal = new Mock<ClaimsPrincipal>(identity);
mockPrincipal.Setup(x => x.Identity).Returns(identity);
mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);

同时,我需要了解:

  1. 如何在C#中设置"aud"和"appId"声明
  2. 如何配置mockPrincipal使其在检查aud有效性时返回false

以下是我要测试的认证代码:

public void Authenticate(JwtBearerOptions options)
{
    _configuration.Bind("AzureAD", options);
    options.TokenValidationParameters.ValidateAudience = true;
    options.TokenValidationParameters.ValidateIssuerSigningKey = true;
    options.TokenValidationParameters.ValidateIssuer = true;

    options.Events ??= new JwtBearerEvents();
    var existingHandlers = options.Events.OnTokenValidated;

    options.Events.OnTokenValidated = async context =>
    {
        string appId = GetAppIdFromToken(context);
        bool isAllowed = await CheckAppIdIsAllowedAsync(context, appId);

        if (isAllowed)
        {
            _logger.LogInformation($"[{nameof(Authenticate)}] AppId in allow list");
        }
        else
        {
            _logger.LogError($"[{nameof(Authenticate)}] AppId {appId} not in allowed list");
        }

        await Task.CompletedTask.ConfigureAwait(false);
    };
    options.Events.OnTokenValidated += existingHandlers;
}

private string GetAppIdFromToken(TokenValidatedContext context)
{
    string appId = context.Principal.Claims.FirstOrDefault(x => x.Type == "appid" || x.Type == "azp")?.Value;
    return appId;
}

private async Task<bool> CheckAppIdIsAllowedAsync(TokenValidatedContext context, string appId)
{
    IEnumerable<string> AllowedApps = _configuration.GetSection("AllowedAppPrincipals").Get<string[]>();
    var FoundAppId = AllowedApps.FirstOrDefault(a => a == appId);
    if (FoundAppId == null)
    {
        context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
        context.Response.ContentType = "application/json";
        const string message = "{\"error\" : \"Unacceptable app principal\"}";
        byte[] arr = Encoding.ASCII.GetBytes(message);
        await context.Response.BodyWriter.WriteAsync(arr);
        context.Fail(message);
        return false;
    }
    return true;
}

解决方案

1. 设置"aud"和"appId"声明

ClaimTypes类没有预定义Azure AD特有的声明类型(比如aud、appid、azp),直接使用字符串作为声明类型即可:

// 包含aud(受众)、appid(客户端ID)的ClaimsIdentity
var identity = new ClaimsIdentity(new[]
{
    new Claim(ClaimTypes.Name, "Sahil"),
    new Claim("aud", "your-valid-audience-value"), // 受众声明
    new Claim("appid", "test-app-id-123") // Azure AD客户端ID声明
});

var mockPrincipal = new Mock<ClaimsPrincipal>(identity);
mockPrincipal.Setup(x => x.Identity).Returns(identity);
mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);

如果需要模拟azp声明(客户端凭据流中也常用此字段表示客户端ID),直接添加new Claim("azp", "test-client-id-456")即可。

2. 模拟Audience验证失败的场景

Audience的验证由JwtBearerOptions.TokenValidationParameters控制,要让验证失败,只需确保模拟的aud值不在ValidAudience或ValidAudiences列表中:

测试步骤:

  1. 配置JwtBearerOptions的TokenValidationParameters,设置与模拟aud不匹配的有效值
  2. 模拟TokenValidatedContext时,传入包含无效aud的ClaimsPrincipal
  3. 触发认证逻辑,验证是否返回401(未授权)或对应的失败结果

示例代码:

// 1. 配置测试用的JwtBearerOptions
var options = new JwtBearerOptions();
var mockConfig = new Mock<IConfiguration>();
// 绑定AzureAD配置(模拟无效的受众)
mockConfig.Setup(c => c.GetSection("AzureAD:ValidAudience")).Returns(new ConfigurationSection("invalid-audience", "invalid-audience"));
var authService = new YourAuthService(mockConfig.Object, Mock.Of<ILogger<YourAuthService>>());
authService.Authenticate(options);

// 2. 模拟包含无效aud的ClaimsPrincipal
var invalidIdentity = new ClaimsIdentity(new[]
{
    new Claim("aud", "wrong-audience-value"),
    new Claim("appid", "test-app-id-123")
});
var invalidPrincipal = new ClaimsPrincipal(invalidIdentity);

// 3. 模拟TokenValidatedContext
var context = new TokenValidatedContext(
    new DefaultHttpContext(),
    Mock.Of<AuthenticationScheme>(),
    options.TokenValidationParameters,
    invalidPrincipal
);

// 注:若aud验证失败,OnTokenValidated事件不会被触发(验证在事件之前完成)
// 若要测试aud验证失败的场景,可直接验证TokenValidationParameters的配置,或手动模拟验证失败状态

3. 测试AppId白名单逻辑

针对CheckAppIdIsAllowedAsync方法,可模拟IConfiguration返回不同白名单,结合包含不同appid的ClaimsPrincipal测试允许/拒绝场景:

// 测试AppId在白名单中的情况
var mockConfig = new Mock<IConfiguration>();
mockConfig.Setup(c => c.GetSection("AllowedAppPrincipals").Get<string[]>())
          .Returns(new[] { "test-app-id-123", "approved-app-456" });

var authService = new YourAuthService(mockConfig.Object, Mock.Of<ILogger<YourAuthService>>());
var context = new TokenValidatedContext(
    new DefaultHttpContext(),
    Mock.Of<AuthenticationScheme>(),
    new TokenValidationParameters(),
    new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("appid", "test-app-id-123") }))
);

bool result = await authService.CheckAppIdIsAllowedAsync(context, "test-app-id-123");
Assert.True(result);

// 测试AppId不在白名单中的情况
mockConfig.Setup(c => c.GetSection("AllowedAppPrincipals").Get<string[]>())
          .Returns(new[] { "approved-app-456" });

result = await authService.CheckAppIdIsAllowedAsync(context, "test-app-id-123");
Assert.False(result);
Assert.Equal((int)HttpStatusCode.Forbidden, context.Response.StatusCode);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 01:20:41