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

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值

  1. 登录Azure门户,进入demoServer的应用注册页面;
  2. 切换到**“公开API”**选项卡;
  3. 在**“标识符URI”**区域,确保api://demoServer-applicationId-inAzureAAD已设置为默认标识符;
  4. 重新获取Token,此时aud会变为api://demoServer-applicationId-inAzureAAD,与服务端配置匹配。

额外验证点

  • 确认服务端的Authority中的租户ID与Token中的tid一致;
  • 检查客户端请求的作用域是否正确关联到服务端的API权限;
  • 验证Token的scp声明包含服务端API所需的权限(如Files.Read)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 03:48:15