如何在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);
同时,我需要了解:
- 如何在C#中设置"aud"和"appId"声明
- 如何配置
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列表中:
测试步骤:
- 配置
JwtBearerOptions的TokenValidationParameters,设置与模拟aud不匹配的有效值 - 模拟
TokenValidatedContext时,传入包含无效aud的ClaimsPrincipal - 触发认证逻辑,验证是否返回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
相关产品推荐
相关产品推荐

