ASP.NET Core Web API 中使用 Microsoft Entra ID 时出现的 401 未授权错误

调用使用 Microsoft Entra ID 身份验证保护的 ASP.NET Core Web API 时,可能会遇到“401 未授权”错误。 本文提供使用JwtBearerEvents获取详细日志以排查这些错误的指南。

症状

使用 [Authorize] 属性 来保护 ASP.NET Core Web API,如下所示:

[Authorize]
public class MyController : ControllerBase
{
    ...
}


public class MyController : ControllerBase
{
   [Authorize]
   public ActionResult<string> Get(int id)
   {
       return "value";
   }
   ...
}

调用 Web API 时,将返回“401 未授权”响应,但消息不包含错误详细信息。

原因

在以下情况下,API 可能会返回“401 未授权”响应:

  • 请求中未包含有效的“Authorization: Bearer”令牌标头。
  • 令牌已过期或不正确:

解决方案

若要调试并解决“401 未授权”错误,请使用 JwtBearerEvents 回调捕获和记录详细的错误信息。 按照以下步骤实现自定义错误处理机制。

JwtBearerEvents 类具有以下回调属性(按以下顺序调用),可帮助你调试这些“401 拒绝访问”或“UnAuthorization”问题:

步骤 1:启用 PII 日志记录

默认情况下,禁用个人身份信息(PII)日志记录。 在用于调试的 Startup.cs 文件的 Configure 方法中启用它。

谨慎

仅在开发环境中使用“Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true”进行调试。 请勿在生产环境中使用它。

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        // The default HSTS value is 30 days. You might want to change this value for production scenarios. See https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }
    // turn on PII logging
    Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;

    app.UseHttpsRedirection();
    app.UseAuthentication();
    app.UseMvc();
}  

步骤 2:创建用于设置异常消息格式的实用工具方法

添加一种方法来整理并展平任何异常信息,以提高可读性。

public static string FlattenException(Exception exception)
{
    var stringBuilder = new StringBuilder();
    while (exception != null)
    {
        stringBuilder.AppendLine(exception.Message);
        stringBuilder.AppendLine(exception.StackTrace);
        exception = exception.InnerException;
    }
    return stringBuilder.ToString();
}

步骤 3:实现 JwtBearerEvents 的回调函数

Startup.cs 方法的 JwtBearerEventsConfigureServices 中配置回调,以处理身份验证事件并记录错误详细信息:

public void ConfigureServices(IServiceCollection services)
{
....
    .AddJwtBearer(options =>
    {
        options.Authority = "https://login.microsoftonline.com/<Tenant>.onmicrosoft.com";
        // if you intend to validate only one audience for the access token, you can use options.Audience instead of
        // using options.TokenValidationParameters which allow for more customization.
        // options.Audience = "10e569bc5-4c43-419e-971b-7c37112adf691";

        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
        {
            ValidAudiences = new List<string> { "<Application ID URI>", "10e569bc5-4c43-419e-971b-7c37112adf691" },
            ValidIssuers = new List<string> { "https://sts.windows.net/<Directory ID>/", "https://sts.windows.net/<Directory ID>/v2.0" }
        };
                
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = ctx =>
            {
                ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
                message += "From OnAuthenticationFailed:\n";
                message += FlattenException(ctx.Exception);
                return Task.CompletedTask;
            },

            OnChallenge = ctx =>
            {
                message += "From OnChallenge:\n";
                ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
                ctx.Response.ContentType = "text/plain";
                return ctx.Response.WriteAsync(message);
            },

            OnMessageReceived = ctx =>
            {
                message = "From OnMessageReceived:\n";
                ctx.Request.Headers.TryGetValue("Authorization", out var BearerToken);
                if (BearerToken.Count == 0)
                    BearerToken = "no Bearer token sent\n";
                message += "Authorization Header sent: " + BearerToken + "\n";
                return Task.CompletedTask;
            },
#For completeness, the sample code also implemented the OnTokenValidated property to log the token claims. This method is invoked when authentication is successful
            OnTokenValidated = ctx =>
            {
                Debug.WriteLine("token: " + ctx.SecurityToken.ToString());
                return Task.CompletedTask;
            }
        };
    });
...
}

示例结果

实现 JwtBearerEvents 回调时,如果出现“401 未授权”错误,响应输出应包括如下示例所示的详细信息:

OnMessageRecieved:

Authorization Header sent: no Bearer token sent.

如果使用 API 开发工具调试请求,应会收到错误详细信息,如以下屏幕截图所示。

API 开发工具中错误详细信息的屏幕截图。

联系我们以获得帮助

如果您有任何疑问或需要帮助,可以创建支持请求,或咨询Azure社区支持。 您还可以向Azure反馈社区提交产品反馈。