Azure AD中JWT Bearer Token受众验证失败问题排查
我有一个本地服务端应用demoServer,已在Azure AD中完成应用注册,基于ASP.NET Core Web API模板开发,负责接收客户端应用的Access Token并完成认证。客户端是本地WPF应用demoClient,同样已在Azure AD中注册应用。
当前测试流程:
- 启动服务端应用;
- 运行WPF客户端,点击登录按钮后跳转至Microsoft登录页面;
- 完成登录操作;
- 获取Access Token;
- 将Token发送至服务端;
- 服务端应完成Token授权并返回响应。
目前步骤1-5均正常,但服务端返回401错误。通过Swagger调用服务端API,粘贴获取的Token后仍得到相同错误:
www-authenticate: Bearer error="invalid_token",error_description="The audience 'demoServer-applicationId-inAzureAAD' is invalid"
解码JWT Token后发现,aud声明的值确实是demoServer-applicationId-inAzureAAD,但服务端仍提示该受众无效。
服务端Program.cs代码
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.Configure(app => { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "weatherForecast"); }); if (app.ApplicationServices.GetService<IWebHostEnvironment>().IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }); webBuilder.ConfigureServices(services => { services.AddControllers(); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = "https://login.microsoftonline.com/demoTenantId/v2.0"; options.Audience = "api://demoServer-applicationId-inAzureAAD"; }); services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); }); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); var securityScheme = new OpenApiSecurityScheme { Name = "Authorization", Description = "JWT Authorization header using the Bearer scheme (Example: 'Bearer 12345abcdef')", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, Scheme = "Bearer", BearerFormat = "JWT", Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }; c.AddSecurityDefinition("Bearer", securityScheme); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { securityScheme, Array.Empty<string>() } }); }); }); }); }
Azure AD配置说明
在demoServer的Azure AD应用注册中,已暴露API:api://demoServer-applicationId-inAzureAAD/Files.Read,且已将demoClient的应用ID添加至授权客户端应用列表,同时已在API权限中添加Files.Read权限。
WPF客户端代码
public partial class MainWindow : Window { public class TokenProvider { private readonly string _clientId; private readonly string _tenantId; private readonly string _redirectUri; public TokenProvider(string clientId, string tenantId, string redirectUri) { _clientId = clientId; _tenantId = tenantId; _redirectUri = redirectUri; } public async Task<string> GetAccessTokenAsync(string[] scopes) { IPublicClientApplication app = PublicClientApplicationBuilder .Create(_clientId) .WithTenantId(_tenantId) .WithRedirectUri(_redirectUri) .Build(); AuthenticationResult result = await app.AcquireTokenInteractive(scopes).ExecuteAsync(); return result.AccessToken; } } private async Task<string> CallApiWithTokenAsync(string accessToken) { using (HttpClient client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); HttpResponseMessage response = await client.GetAsync("https://localhost-Url-For-ServerApp/Demo"); if (response.IsSuccessStatusCode) { return await response.Content.ReadAsStringAsync(); } else { throw new Exception($"API call failed with status code {response.StatusCode}"); } } } private async void LoginButton_Click(object sender, RoutedEventArgs e) { string[] scopes = new string[] { "api://demoServer-applicationId-inAzureAAD/Files.Read", "api://demoServer-applicationId-inAzureAAD/user_impersonation" }; TokenProvider tokenProvider = new TokenProvider("demoClient-applicationId-inAzureAAD", "TenantID", "https://localhost-Url-For-ServerApp"); string accessToken = await tokenProvider.GetAccessTokenAsync(scopes); Debug.WriteLine(accessToken); // Send the access token to the server app for authentication. await CallApiWithTokenAsync (accessToken); } public MainWindow() { InitializeComponent(); } }
示例Token
{ "typ": "JWT", "alg": "RS256", "kid": "-XXXXXXXXXXXXXXXXXXXX" }.{ "aud": "demoServer-applicationId-inAzureAAD", "iss": "https://login.microsoftonline.com/tenantID/v2.0", "iat": ###########, "nbf": ###########, "exp": ###########, "aio": "######################################", "azp": "demoClient-applicationId-inAzureAAD", "azpacr": "0", "name": "My Name", "oid": "someID", "preferred_username": "myEmail", "rh": "######################.", "scp": "Demo Files.Read user_impersonation", "sub": "######################", "tid": "tenantID", "uti": "#######################", "ver": "2.0" }.[Signature]
核心原因
服务端配置的Audience是api://demoServer-applicationId-inAzureAAD,但Token中的aud是demoServer-applicationId-inAzureAAD(没有前缀api://),两者不匹配,导致JWT验证失败。
Azure AD针对v2.0端点生成的Token,当客户端请求的作用域是api://{client-id}/{scope}时,默认返回的aud值是应用程序ID,而非完整的api://格式标识符。
解决办法
有两种可行的修正方案:
方案一:修改服务端的Audience配置
将服务端AddJwtBearer中的Audience改为不带api://前缀的应用ID:
.AddJwtBearer(options => { options.Authority = "https://login.microsoftonline.com/demoTenantId/v2.0"; options.Audience = "demoServer-applicationId-inAzureAAD"; });
方案二:配置Azure AD应用的标识符URI,强制Token返回完整aud值
- 登录Azure门户,进入demoServer的应用注册页面;
- 切换到**“公开API”**选项卡;
- 在**“标识符URI”**区域,确保
api://demoServer-applicationId-inAzureAAD已设置为默认标识符; - 重新获取Token,此时
aud会变为api://demoServer-applicationId-inAzureAAD,与服务端配置匹配。
额外验证点
- 确认服务端的
Authority中的租户ID与Token中的tid一致; - 检查客户端请求的作用域是否正确关联到服务端的API权限;
- 验证Token的
scp声明包含服务端API所需的权限(如Files.Read)。
内容的提问来源于stack exchange,提问作者MChak

