为何JwtSecurityTokenHandler读取的Token字段少于jwt.io?求排查
JWT验证异常:ReadJwtToken无法读取exp等字段
问题现象
- 在
ValidateToken接口中,通过JwtSecurityTokenHandler.ReadJwtToken解析JWT时,发现缺失exp(值为1752128418)在内的3个字段 - 打印Token并验证显示所有字段完整,但代码读取时
jwt.Payload.Expiration为null - 使用与
GenerateJwtToken完全一致的TokenValidationParameters执行验证,结果无变化 - 其他使用该Token的授权端点可正常工作
验证方法代码
public async Task<ActionResult<object>> ValidateToken() { var authHeader = HttpContext.Request.Headers["Authorization"].FirstOrDefault(); var token = authHeader.Substring("Bearer ".Length).Trim(); Console.WriteLine(token.ToString()); var tokenHandler = new JwtSecurityTokenHandler(); var jwt = tokenHandler.ReadJwtToken(token); if (jwt.Payload.Expiration == null) { return Unauthorized("Token missing 'exp' claim."); } }
验证参数代码
var key = Encoding.UTF8.GetBytes(_config["Jwt:Key"]); tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = true, ValidIssuer = _config["Jwt:Issuer"], ValidateAudience = true, ValidAudience = _config["Jwt:Audience"], ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(1) }, out SecurityToken validatedToken);
Token生成方法
private string GenerateJwtToken(Account account) { var claims = new[] { new Claim(ClaimTypes.NameIdentifier, account.Id.ToString()), new Claim(ClaimTypes.Name, account.Username), new Claim(ClaimTypes.Role, account.Role.ToString()) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _config["Jwt:Issuer"], audience: _config["Jwt:Audience"], claims: claims, expires: DateTime.UtcNow.AddDays(7), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); }
引用命名空间
using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt;
日志信息
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjEwIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6Im1vc3RhZmHigIsiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJQbGF5ZXIiLCJleHAiOjE3NTIxMjg0MTgsImlzcyI6IkdhbWVTZXJ2ZXJBUEkiLCJhdWQiOiJHYW1lQ2xpZW50cyJ9.zMCtvt6lyHPbNMkUmyociTVUwPp81qwF1gcBaOzfsNo {"alg":"HS256"}.{"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier":"10","http://schemas.microsoft.com/ws/2008/06/identity/claims/role":"Player","iss":"GameServerAPI"}
排查方向与解决方案
确认Token完整性
在代码中添加Token长度校验,确保从Authorization头截取的Token与日志中完整Token一致:Console.WriteLine($"Token length: {token.Length}");若长度不一致,改用更安全的截取方式避免格式异常:
var token = authHeader?.Replace("Bearer ", "", StringComparison.OrdinalIgnoreCase).Trim();直接访问Payload原始字典
部分标准字段可能因映射逻辑未赋值到对应属性,但会保留在Payload字典中,尝试直接读取:if (!jwt.Payload.TryGetValue("exp", out var expValue) || expValue == null) { return Unauthorized("Token missing 'exp' claim."); } // 转换为DateTime格式 var expiration = DateTimeOffset.FromUnixTimeSeconds(long.Parse(expValue.ToString())).UtcDateTime;检查JWT Handler配置
确认是否设置了MapInboundClaims = false,该配置会关闭标准Claim的自动映射,导致Payload.Expiration等属性无法赋值:var tokenHandler = new JwtSecurityTokenHandler { MapInboundClaims = true // 确保为默认值true };验证ValidateToken返回的Token
调用ValidateToken后,将返回的validatedToken转换为JwtSecurityToken,验证流程可能修复字段映射问题:var validatedJwt = validatedToken as JwtSecurityToken; if (validatedJwt?.Payload.Expiration == null) { return Unauthorized("Token missing 'exp' claim."); }检查NuGet包版本
确保System.IdentityModel.Tokens.Jwt和Microsoft.IdentityModel.Tokens包版本一致且为最新稳定版,旧版本可能存在字段解析Bug。
内容的提问来源于stack exchange,提问作者mostafa khaleghi
相关产品推荐
相关产品推荐

