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

.NET Core 3.1 API中Azure生成JWT Token验证失败:签名密钥配置问题求助

解决Azure AD JWT验证失败的问题

我来帮你搞定这个验证失败的问题——核心原因是你用错了签名密钥的类型和来源,Azure AD签发的JWT是用RSA非对称加密签名的,而你现在用的应用密钥(客户端密码)是用来获取Token的凭证,和签名验证完全不搭边,这才导致验证一直失败。

先纠正几个明显的代码笔误

你的Startup.cs里有两处拼写错误,这会直接导致中间件无法正常工作:

// 错误写法
app.UserAuthorization();
app.UserMiddleware<JWTMiddleware>();

// 正确写法
app.UseAuthorization();
app.UseMiddleware<JWTMiddleware>();

而且中间件顺序很重要,自定义JWT中间件要放在授权中间件之前,完整的顺序应该是:

app.UseRouting();
app.UseMiddleware<JWTMiddleware>(); // 自定义JWT中间件在前
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

核心问题:Azure AD的JWT签名机制

Azure AD用自己的RSA私钥签名JWT,验证时必须用对应的公钥——而这个公钥不是你的应用密钥,而是Azure AD公开的签名密钥,它会定期轮换,所以我们需要从Azure AD的OpenID元数据端点自动获取,不能硬编码。

修改后的完整JWTMiddleware代码

下面是修正后的中间件,它会自动从Azure AD元数据获取签名密钥,正确验证Token:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;

namespace WebAPI
{
    public class JwtMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IConfigurationManager<OpenIdConnectConfiguration> _configManager;
        private readonly string _tenantId;
        private readonly string _clientId;

        public JwtMiddleware(RequestDelegate next, string tenantId, string clientId)
        {
            _next = next;
            _tenantId = tenantId;
            _clientId = clientId;

            // 初始化配置管理器,自动从Azure AD元数据获取签名密钥
            var metadataUrl = $"https://login.microsoftonline.com/{_tenantId}/v2.0/.well-known/openid-configuration";
            _configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
                metadataUrl,
                new OpenIdConnectConfigurationRetriever(),
                new HttpDocumentRetriever());
        }

        public async Task Invoke(HttpContext context)
        {
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
            if (token != null)
                await AttachAccountToContext(context, token);

            await _next(context);
        }

        private async Task AttachAccountToContext(HttpContext context, string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                // 获取最新的Azure AD配置(包含当前有效的签名密钥)
                var adConfig = await _configManager.GetConfigurationAsync(context.RequestAborted);

                var validationParams = new TokenValidationParameters
                {
                    ValidIssuer = $"https://login.microsoftonline.com/{_tenantId}/v2.0",
                    ValidAudience = _clientId,
                    ValidateIssuer = true, // 必须开启 issuer 验证
                    ValidateAudience = true, // 必须开启 audience 验证
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKeys = adConfig.SigningKeys, // 使用Azure AD的公钥集合
                    ClockSkew = TimeSpan.FromMinutes(5) // 允许5分钟的时钟偏差,避免服务器时间差导致验证失败
                };

                // 验证Token并获取用户身份信息
                var userPrincipal = tokenHandler.ValidateToken(token, validationParams, out _);

                // 注意:upn是用户邮箱,不能转成int!用oid(Azure AD用户对象ID,GUID格式)或sub作为用户标识
                var userId = userPrincipal.Claims.First(x => x.Type == "oid").Value;

                // 将用户ID附加到请求上下文
                context.Items["Account"] = userId;
            }
            catch (Exception ex)
            {
                // 建议在这里添加日志记录,方便排查验证失败的具体原因
                // _logger.LogError(ex, "JWT验证失败,Token:{Token}", token);
            }
        }
    }

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class AuthorizeAttribute : Attribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var userId = context.HttpContext.Items["Account"];
            if (userId == null)
            {
                context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
            }
        }
    }
}

配置和注册中间件

  1. 在appsettings.json里添加Azure AD的配置信息:
"AzureAd": {
  "TenantId": "你的Azure AD租户ID",
  "ClientId": "你的应用注册ClientID"
}
  1. 在Startup.cs里读取配置并注册中间件:
// 在ConfigureServices方法中读取配置
var tenantId = Configuration["AzureAd:TenantId"];
var clientId = Configuration["AzureAd:ClientId"];

// 在Configure方法中注册中间件
app.UseRouting();
app.UseMiddleware<JwtMiddleware>(tenantId, clientId); // 传入租户ID和ClientID
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

关键知识点说明

  • 为什么不用应用密钥:应用密钥是客户端向Azure AD请求Token时的身份凭证,和JWT的签名验证毫无关系,不能用来作为签名密钥。
  • 签名密钥来源:Azure AD的公钥会定期轮换,通过元数据端点https://login.microsoftonline.com/{租户ID}/v2.0/.well-known/openid-configuration可以获取当前所有有效的签名密钥,用ConfigurationManager可以自动刷新这些密钥。
  • 用户标识的选择:upn是用户的邮箱字符串,不能转成int,应该用oid(Azure AD用户唯一对象ID)或sub(JWT主题ID)作为用户标识。
  • ClockSkew设置:由于不同服务器可能存在时间差,设置5分钟的偏差可以避免因时间不一致导致的验证失败。

验证排查步骤

如果还是有问题,可以用jwt.io解析前端传递的Token,检查以下内容:

  • aud声明是否等于你的应用注册ClientID
  • iss声明是否等于https://login.microsoftonline.com/{你的租户ID}/v2.0
  • exp声明是否未过期

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 21:03:10