Onion架构下JWT令牌解析失败问题求助
各位大佬,我在基于Onion架构搭建的微服务里碰到了一个头疼的JWT解析问题,折腾好几天都没搞定,特意来求助!
情况是这样的:我按照常规方式实现了JWT认证,在单体架构项目里完全正常,但拆分成Onion架构之后,就出现了解析失败的错误。日志里明明显示收到了格式正确的Token,但系统就是报错说Token格式不对,没有分隔点。
先贴一下我的关键代码:
1. Program.cs 中的认证配置
// ... builder.Services.AddControllers() .ConfigureApiBehaviorOptions(options => { options.SuppressModelStateInvalidFilter = true; }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddInfrastructureSwagger(); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = configuration["Jwt:Issuer"], // Correspondente ao issuer ValidAudience = configuration["Jwt:Audience"], // Correspondente à audience IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]!)) }; // Adicionando logs de erro para melhor depuração options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { Console.WriteLine($"Authentication failed: {context.Exception.Message}"); Console.WriteLine($"Token recebido: {context.Request.Headers["Authorization"]}"); return Task.CompletedTask; } }; }); // ...
2. TokenController.cs (生成Token的控制器)
namespace MyAPI.Core.Microservice.API.Controllers; using MyAPI.Core.Microservice.Domain.Models.Entities; using Microsoft.Extensions.Configuration; using System.IdentityModel.Tokens.Jwt; using Microsoft.IdentityModel.Tokens; using Microsoft.AspNetCore.Mvc; using System.Text; using Microsoft.Extensions.Logging; /// <summary> /// Controlador responsável pela gestão do token de autenticação. /// </summary> [ApiController] [Route("api/auth-token")] public class TokenController : ControllerBase { private readonly string chaveSecreta; private readonly string issuer; private readonly string audience; private readonly string AdmUsername; private readonly string AdmPassword; private readonly ILogger<TokenController> _logger; /// <summary> /// Construtor do TokenController. /// </summary> /// <param name="configuration">Configurações da aplicação, contendo as informações de JWT e credenciais de administrador.</param> /// <param name="logger">Logger para registrar eventos e erros.</param> /// <exception cref="ArgumentNullException">Lançado quando as configurações de JWT ou administrador estão ausentes.</exception> public TokenController(IConfiguration configuration, ILogger<TokenController> logger) { issuer = configuration["Jwt:Issuer"]!; audience = configuration["Jwt:Audience"]!; chaveSecreta = configuration["Jwt:Key"]!; AdmUsername = configuration["Adm:Username"]!; AdmPassword = configuration["Adm:Password"]!; _logger = logger; } /// <summary> /// Criar um novo token /// </summary> /// <param name="admUser">Objeto contendo as credenciais do administrador</param> /// <returns>Token JWT gerado</returns> [HttpPost] [ProducesResponseType(typeof(string), 201)] // Sucesso na criação do token: 200 Good Request [ProducesResponseType(typeof(object), 400)] // Erro de validação: 400 Bad Request [ProducesResponseType(typeof(object), 500)] // Erro do servidor: 500 Internal Server Error public ActionResult<string> GetToken(AdmUser admUser) { if( admUser.Username == AdmUsername && admUser.Password == AdmPassword) { var tokenJwt = GerarTokenJwt(admUser.Username); return Ok(tokenJwt); } _logger.LogWarning("Administrative credentials incorrect for user: {Username}", admUser.Username); return BadRequest("Administrative credentials incorrect."); } private string GerarTokenJwt(string username) { var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(chaveSecreta)); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var claims = new System.Security.Claims.Claim[] { new System.Security.Claims.Claim("username", username), }; var token = new JwtSecurityToken( issuer: issuer, audience: audience, claims: claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: credentials ); return new JwtSecurityTokenHandler().WriteToken(token); } }
3. DependencyInjectionSwagger.cs (注意:此处代码疑似粘贴错误,当前内容与TokenController完全一致)
namespace MyAPI.Core.Microservice.API.Controllers; using MyAPI.Core.Microservice.Domain.Models.Entities; using Microsoft.Extensions.Configuration; using System.IdentityModel.Tokens.Jwt; using Microsoft.IdentityModel.Tokens; using Microsoft.AspNetCore.Mvc; using System.Text; using Microsoft.Extensions.Logging; /// <summary> /// Controlador responsável pela gestão do token de autenticação. /// </summary> [ApiController] [Route("api/auth-token")] public class TokenController : ControllerBase { private readonly string chaveSecreta; private readonly string issuer; private readonly string audience; private readonly string AdmUsername; private readonly string AdmPassword; private readonly ILogger<TokenController> _logger; /// <summary> /// Construtor do TokenController. /// </summary> /// <param name="configuration">Configurações da aplicação, contendo as informações de JWT e credenciais de administrador.</param> /// <param name="logger">Logger para registrar eventos e erros.</param> /// <exception cref="ArgumentNullException">Lançado quando as configurações de JWT ou administrador estão ausentes.</exception> public TokenController(IConfiguration configuration, ILogger<TokenController> logger) { issuer = configuration["Jwt:Issuer"]!; audience = configuration["Jwt:Audience"]!; chaveSecreta = configuration["Jwt:Key"]!; AdmUsername = configuration["Adm:Username"]!; AdmPassword = configuration["Adm:Password"]!; _logger = logger; } /// <summary> /// Criar um novo token /// </summary> /// <param name="admUser">Objeto contendo as credenciais do administrador</param> /// <returns>Token JWT gerado</returns> [HttpPost] [ProducesResponseType(typeof(string), 201)] // Sucesso na criação do token: 200 Good Request [ProducesResponseType(typeof(object), 400)] // Erro de validação: 400 Bad Request [ProducesResponseType(typeof(object), 500)] // Erro do servidor: 500 Internal Server Error public ActionResult<string> GetToken(AdmUser admUser) { if( admUser.Username == AdmUsername && admUser.Password == AdmPassword) { var tokenJwt = GerarTokenJwt(admUser.Username); return Ok(tokenJwt); } _logger.LogWarning("Administrative credentials incorrect for user: {Username}", admUser.Username); return BadRequest("Administrative credentials incorrect."); } private string GerarTokenJwt(string username) { var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(chaveSecreta)); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var claims = new System.Security.Claims.Claim[] { new System.Security.Claims.Claim("username", username), }; var token = new JwtSecurityToken( issuer: issuer, audience: audience, claims: claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: credentials ); return new JwtSecurityTokenHandler().WriteToken(token); } }
收到的错误信息
Authentication failed: IDX14100: JWT is not well formed, there are no dots (.).
The token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.
Token recebido: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluQHdhcnJlbi5kb21haW4uY29tIiwiZXhwIjoxNzQwNjA1MjkzLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ.gZXRCeAbphb-WTqu3g3KQFjVyqJPULH31hRreQxnYpk
排查方向与解决方案建议:
修复DependencyInjectionSwagger.cs的错误内容
这个文件应该是Swagger的配置扩展类(对应AddInfrastructureSwagger方法),但当前内容和TokenController完全重复,这绝对是问题核心之一。正确的Swagger配置需要包含JWT认证支持,示例如下:using Microsoft.OpenApi.Models; using Microsoft.Extensions.DependencyInjection; namespace MyAPI.Core.Microservice.API.Client.DependencyInjection; public static class DependencyInjectionSwagger { public static IServiceCollection AddInfrastructureSwagger(this IServiceCollection services) { services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "MyAPI", Version = "v1" }); // 配置JWT认证的Swagger支持 var securityScheme = new OpenApiSecurityScheme { Name = "Authorization", Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", In = ParameterLocation.Header, Description = "请输入Bearer {Token}格式的认证令牌", Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }; c.AddSecurityDefinition("Bearer", securityScheme); // 全局要求认证 c.AddSecurityRequirement(new OpenApiSecurityRequirement { { securityScheme, Array.Empty<string>() } }); }); return services; } }检查中间件管道的执行顺序
中间件顺序直接影响认证逻辑的生效,必须确保认证在授权之前执行,正确的Program.cs管道顺序应该是:// 注册完所有服务之后 app.UseSwagger(); app.UseSwaggerUI(); app.UseHttpsRedirection(); app.UseAuthentication(); // 先执行认证 app.UseAuthorization(); // 再执行授权 app.MapControllers(); // 最后映射控制器验证JWT配置参数的一致性
确保Program.cs和TokenController读取的Jwt配置(Issuer、Audience、Key)完全一致,检查appsettings.json中的配置是否正确:{ "Jwt": { "Issuer": "http://localhost:5000", "Audience": "http://localhost:5000", "Key": "至少16位长度的密钥,比如yourStrongSecretKey12345" }, "Adm": { "Username": "admin", "Password": "yourAdminPassword" } }注意密钥长度不能太短,否则HmacSha256算法会触发验证错误。
修正TokenController中的注释错误
你在ProducesResponseType(201)的注释里写的是200,但实际返回的Ok()对应200状态码,建议统一改成[ProducesResponseType(typeof(string), StatusCodes.Status200OK)],避免混淆。
按照上面的步骤排查,应该能解决问题。我怀疑最核心的问题就是Swagger配置文件粘贴错误,导致Swagger没有正确处理JWT令牌的传递,或者中间件顺序不对。
备注:内容来源于stack exchange,提问作者rodrigo

