使用Microsoft.IdentityModel.Tokens创建解析JWT令牌异常求助
需要创建包含自定义Claims的JWT令牌,之后验证该令牌并获取ClaimsPrincipal对象。使用的库:
using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt;
生成令牌的代码如下:
var handler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(AuthSettings.PrivateKey); var credentials = new SigningCredentials( new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature); var claims = new List<Claim> { new(JwtRegisteredClaimNames.Sub, user.Username), new(JwtRegisteredClaimNames.Jti, user.ID.ToString()), new(JwtRegisteredClaimNames.NameId, user.ID.ToString()), new(ClaimTypes.NameIdentifier, user.ID.ToString()), new(ClaimTypes.Role, user.UserPermitionLevel.ToString()), }; var identity = new GenericIdentity(user.Username); var claimsidentity = new ClaimsIdentity(identity, claims); DateTime.UtcNow.AddMinutes(AuthSettings.ExpiryMinutes).ToString()) var tokenDescriptor = new SecurityTokenDescriptor { Subject = claimsidentity, Expires = DateTime.UtcNow.AddMinutes(AuthSettings.ExpiryMinutes), SigningCredentials = credentials, }; var token = handler.CreateToken(tokenDescriptor); var tk = handler.WriteToken(token); var res1 = handler.ReadJwtToken(tk); var res2 = handler.ReadToken(tk); return handler.WriteToken(token);
生成的令牌Payload包含完整的Claims信息,但调用handler.ReadToken(tk)或handler.ReadJwtToken(tk)读取时,Payload及Claims仅显示4个元素,请求排查解决。
1. 先修复语法错误
代码中有一行无意义且语法错误的代码,直接删除:
// 此行无效,删除 DateTime.UtcNow.AddMinutes(AuthSettings.ExpiryMinutes).ToString())
2. ClaimsIdentity构造参数错误(核心问题)
你创建ClaimsIdentity时传参逻辑错误:
// 错误:第二个参数是认证类型字符串,不是Claims集合 var claimsidentity = new ClaimsIdentity(identity, claims);
ClaimsIdentity(IIdentity identity, string authenticationType)的第二个参数是认证类型标识,不是Claims列表。这会导致你定义的5个Claims没有被正确关联到ClaimsIdentity中,后续生成令牌时可能出现Claims缺失或重复映射的问题。
修正后的ClaimsIdentity创建方式:
// 直接传入Claims集合与认证类型,无需额外创建GenericIdentity var claimsIdentity = new ClaimsIdentity(claims, "HmacSha256Auth");
3. 读取时Claims数量减少的原因
你看到的4个Claims是因为:JwtRegisteredClaimNames.NameId和ClaimTypes.NameIdentifier本质是同一个Claim的不同命名(前者是JWT标准字段名,后者是.NET内置Claim类型名),在解析时会被自动合并为一个条目。加上sub、jti、role,正好是4个,和你观察到的数量一致。
如果要查看原始未合并的Payload内容,可以直接解析JWT的Base64部分:
var payloadBase64 = tk.Split('.')[1]; // 修复Base64 URL编码的字符替换 var payloadBytes = Convert.FromBase64String(payloadBase64.Replace('-', '+').Replace('_', '/')); var rawPayload = Encoding.UTF8.GetString(payloadBytes); // rawPayload会显示完整的原始Claims
4. 正确验证令牌并获取ClaimsPrincipal
要获取包含完整Claims的ClaimsPrincipal,需要使用ValidateToken方法(而非仅读取令牌),这也是生产环境中验证JWT的标准方式:
var tokenString = "生成的JWT令牌"; var handler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(AuthSettings.PrivateKey); var validationParams = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = false, // 根据业务需求开启/关闭 ValidateAudience = false, // 根据业务需求开启/关闭 ClockSkew = TimeSpan.Zero // 关闭时钟偏差容忍 }; // 验证令牌并获取ClaimsPrincipal var claimsPrincipal = handler.ValidateToken(tokenString, validationParams, out _); // 获取所有Claims var allClaims = claimsPrincipal.Claims.ToList();
内容的提问来源于stack exchange,提问作者Martin

