.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 }; } } } }
配置和注册中间件
- 在
appsettings.json里添加Azure AD的配置信息:
"AzureAd": { "TenantId": "你的Azure AD租户ID", "ClientId": "你的应用注册ClientID" }
- 在
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声明是否等于你的应用注册ClientIDiss声明是否等于https://login.microsoftonline.com/{你的租户ID}/v2.0exp声明是否未过期
内容的提问来源于stack exchange,提问作者codingBeast
相关产品推荐
相关产品推荐

