@iEricLee Creation Time : 2024-09-11 11:06:03 Last Modification Time : 2024-09-11 12:47:54
在前面我们已经完成了 在 .NET Core 中生成和解析JWT令牌(1):基础知识和准备工作 ,本文中将通过示例演示使用 HS256(HMAC-SHA256)签名算法创建JWT令牌。
为了方便代码管理,将这个示例的核心代码封装在一个单独的类 Jwt_HMACSHA256Helper
中。
/// <summary>
/// JwtToken生成、校验(HMACSHA256)
/// </summary>
public static class Jwt_HMACSHA256Helper
{
//密钥
private static string KEY = "f47b558d-7654-458c-99f2-123456ef0199";
//todo
}
接下来我们实现生成JWT令牌的方法。
/// <summary>
/// 生成token
/// </summary>
/// <returns></returns>
public static string GetJwtToken()
{
//对称秘钥
SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(KEY));
var claims = new List<Claim>() {
new Claim("ID",Guid.NewGuid().ToString()),
new Claim("Name","ZhiShiLe")
};
JwtSecurityToken jwtToken = new JwtSecurityToken(
issuer: "auth.zhishile.com",
audience: "ZhiShiLe.Client",
claims: claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddSeconds(10),
signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
);
string token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
return token;
}
签名凭据 SigningCredentials
构造函数接收两个参数:密钥和算法,这里使用对称密钥 SymmetricSecurityKey
和 SecurityAlgorithms.HmacSha256
算法。
在 Program.cs
中调用 GetJwtToken()
方法,并打印出生成的令牌。
var token = Jwt_HMACSHA256Helper.GetJwtToken();
Console.WriteLine(token);
执行 dotnet run
运行程序,并打印出生成的令牌。
我们得到以下输出结果,一个通过 HS256 算法进行签名的JWT令牌:(以实际运行结果为准)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6IjdiMGYzOTlmLWI2NWUtNGE4Mi1iY2RlLWM0MzkyNDZlNDFkNiIsIk5hbWUiOiJaaGlTaGlMZSIsIm5iZiI6MTcyNDIzNzY1MCwiZXhwIjoxNzI0MjM3NjYwLCJpc3MiOiJhdXRoLnpoaXNoaWxlLmNvbSIsImF1ZCI6IlpoaVNoaUxlLkNsaWVudCJ9.rBvn8IwTvoUPmPQvDt661B7pW-AyuR2XVZ7e_SAaVRY
根据JWT格式规范,生成的令牌是一串字符串,由 .
分隔的三部分组成:HEADER
.PAYLOAD
.SIGNATURE
。
其中第三部分 SIGNATURE
是签名,用于校验,防止数据被篡改。
接下来,我们使用 https://jwt.io 手动校验和解析令牌。
如下图所示,将令牌内容粘贴到网站中,正确解析出 Header 和 Payload 内容。
将密钥粘贴到 VERIFY SIGNATURE 输入框中,显示 Verify Signature ,表示验证签名成功。
手动验证可以快速查看令牌内容和验证密钥签名是否正确,但是通常我们需要在程序中对令牌进行校验和解析。
在 Jwt_HMACSHA256Helper
类中添加一个 VerifyJwtToken
方法,用于校验和解析令牌,接收令牌内容,返回校验结果和一个 ClaimsPrincipal
实例。
/// <summary>
/// 校验token
/// </summary>
/// <param name="token"></param>
/// <param name="principal"></param>
/// <returns></returns>
public static bool VerifyJwtToken(string token, out ClaimsPrincipal principal)
{
}
JwtSecurityTokenHandler
提供直接解析JWT字符串返回 JwtSecurityToken
实例的方法:
var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token);
JwtSecurityTokenHandler
还提供了校验令牌的方法 JwtSecurityTokenHandler.ValidateToken
,接收3个参数,返回 ClaimsPrincipal
实例。
第一个参数是令牌内容,一个字符串;第二个参数是令牌验证参数 TokenValidationParameters
用于设置令牌校验参数,包括:
ValidateLifetime
是否验证生命周期。ValidateAudience
是否验证接收者。ValidateIssuer
是否验证颁发者。ValidateIssuerSigningKey
是否验证签名。ValidIssuer
获取或设置一个字符串,该字符串表示将用于检查令牌的颁发者的有效颁发者。默认值为null。ValidAudience
获取或设置一个字符串,该字符串表示将用于检查令牌受众的有效受众。默认值为null。IssuerSigningKey
获取或设置用于签名验证的密钥。ClockSkew
获取或设置验证时间时应用的时钟偏差。注意:要将密钥转换为 ASCII 编码,构建一个 SecurityKey
实例。
通常我们希望直接返回 SecurityToken
实例,通过第三个参数返回,这里用到 out
关键字。
principal = null;
SecurityToken validatedToken = null;
//秘钥
SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(KEY));
//校验token
var validateParameter = new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Issuer,
ValidAudience = Audience,
IssuerSigningKey = securityKey,
ClockSkew = TimeSpan.Zero //校验过期时间设置此属性
};
try
{
//校验并解析token,validatedToken是解密后的对象
principal = new JwtSecurityTokenHandler().ValidateToken(token, validateParameter, out validatedToken);
//获取payload中的数据
//var jwtPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson();
}
catch (SecurityTokenExpiredException ex)
{
//表示过期
}
catch (SecurityTokenException ex)
{
//表示token错误
}
catch (Exception ex)
{
//其他异常
}
return validatedToken;
由于输入参数 token
是字符串,并且不能确保用户输入的字符串是完全合法的 JWT令牌内容,因此在对 token
进行验证的同时,使用 try...catch
进行异常处理。