2.JWT
2.1 知识点
JWT
什么是JWT?
JWT,全称为 JSON Web Token,是一种用于传递凭证的方式。它能帮助系统识别、读取、解析和验证用户凭证,并据此进行授权。简单来说,JWT 就像一张数字化的“入场券”,它自包含了用户身份信息和权限数据,能够被用来证明持有者的合法性。
JWT的核心特性
- 无状态化:JWT 将会话状态保存在客户端,而非服务器端。这样,服务器无需管理繁杂的会话数据,能大幅减轻存储和维护负担。
- 自包含性:JWT 内嵌了用户身份、权限、过期时间等必要信息,服务器无需每次都查询数据库就能验证用户身份,加快了响应速度。
- 安全性:借助签名机制(如 HMAC 或 RSA),JWT 能确保令牌的完整性和合法性。只有持有签名密钥的服务才能生成有效的令牌,有效防止数据篡改。
- 易于集成:采用标准化的 JSON 格式,JWT 可轻松解析,且受到众多现代编程语言和框架的支持,使得系统集成变得简单高效。
- 灵活的有效期:JWT 允许开发者为每个令牌设置自定义的过期时间,并支持通过刷新机制延长会话,而无需频繁登录验证。
为什么选择JWT?
- 去中心化:利用 JWT 的去中心化特性,各个逻辑服务器不再依赖于集中式的注册登录服务器。这样,每个服务器可以独立验证用户凭证,使得整个系统更加灵活、解耦且易于扩展。
- 安全性保障:JWT 的数字签名机制确保了数据在传输过程中不会被篡改,同时多节点都可以独立验证令牌,有效提高了分布式系统的安全性和可靠性。
- 通用性强:由于采用标准化格式,JWT 能轻松与第三方应用对接,为多平台、多语言环境下的信息交换提供了便捷的解决方案。
何时使用JWT?
- 用户登录:当用户成功登录后,系统会颁发一个 JWT。这个令牌不仅包含用户身份和权限数据,还设有有效期。服务器通过解析 JWT 快速识别用户访问权限,从而提供个性化服务和资源分配。
- 安全信息交换:JWT 也常用于系统间的安全数据传输,确保传递的信息在过程中保持完整且未被篡改。
JWT的签名验证
JWT的结构
JWT(JSON Web Token)是一种用于在各方之间安全传递信息的开放标准。它主要由三部分组成,每部分之间用点(.
)分隔:
- Header(头部):通常包含令牌类型(JWT)和所使用的签名算法(如HS256、RS256等)。
- Payload(载荷):存放用户的身份信息、权限、过期时间等数据,这部分是自包含的。
- Signature(签名):通过将Header和Payload使用指定的加密算法进行签名生成的,用于确保数据在传输过程中不被篡改。
更多详细内容可以访问 jwt.io 进行学习和测试。
JWT的信任问题
在分布式系统中,各个服务器如何保证“大门入口”能够准确识别并信任颁发的“门票”(JWT)?
- 信任机制:通过使用加密算法生成数字签名,JWT能让验证者确认令牌的来源和完整性。
- 防篡改:只有拥有密钥的签名者才能生成有效的令牌,任何篡改都会导致签名验证失败,从而保障数据安全。
JWT的加密算法
JWT的安全性依赖于所使用的加密算法,这里常见的有以下几种:
HS256(对称加密算法)
- 双方仅共享一个密钥,用于生成和验证签名。
- 由于依赖于同一个密钥,因此密钥必须妥善保管,避免泄露。
RS256(非对称加密算法,RSA 签名结合 SHA-256 散列算法)
- 使用公钥和私钥对。签发方使用私钥生成签名,而验证方使用公钥来验证签名。
- 类似于验钞机与印钞机的关系,印钞机负责出钞(签名),验钞机则负责验证钞票真伪(签名验证)。
HMACSHA256
- 这是一种不可逆的哈希算法,可将任意长度的字符串转换为固定长度且唯一的摘要,用于生成签名,确保数据一致性。可以理解为类似md5码
- 实际应用中,HMACSHA256经常被用作HS256算法的具体实现,提供防篡改保护。
JWT的加密过程详解
JWT的生成过程可以简单概括为以下步骤,以header.payload.signature
和加密算法为HMACSHA256(使用其他加密算法的步骤类似)为例说明:
编码Header和Payload
- 对JWT的头部和载荷部分分别进行Base64Url编码:
var encodedHeader = base64UrlEncode(header); var encodedPayload = base64UrlEncode(payload); var headerAndPayload = encodedHeader + "." + encodedPayload;
- 这里的
headerAndPayload
即为JWT的前两部分,用于后续的签名计算。
- 对JWT的头部和载荷部分分别进行Base64Url编码:
生成签名
- 根据所选择的加密算法(如HMACSHA256),对
headerAndPayload
进行签名计算,生成固定长度的签名字符串:var signature = HMACSHA256(headerAndPayload);
- 这个过程确保只有知道密钥或私钥的一方才能生成有效签名,从而防止数据被篡改。
- 根据所选择的加密算法(如HMACSHA256),对
构造最终JWT
- 将编码后的Header和Payload与签名组合,形成完整的JWT:
var jwt = headerAndPayload + "." + signature;
- 这个JWT令牌就是系统在用户登录后颁发的“门票”,后续各个服务通过验证签名来确认令牌的合法性。
- 将编码后的Header和Payload与签名组合,形成完整的JWT:
总结
JWT凭借其结构化的设计和灵活的加密算法,为现代分布式系统提供了一种高效、无状态的身份验证方式。通过数字签名机制,JWT不仅简化了服务端的会话管理,还大大提高了数据传输的安全性。无论是HS256的对称加密,还是RS256的非对称加密,都为JWT构建了坚实的安全屏障,使得不同系统间的信任验证变得简单而高效。
RSA
RSA是什么
RSA 是一种非对称加密算法,它使用一对密钥——公钥和私钥。私钥用于数字签名或解密,而公钥则用于验证签名或加密数据。RSA 的安全性基于大整数分解的数学难题。
RSA和JWT的关系
- 在 JWT 中,签名部分的主要作用是确保数据在传输过程中不被篡改,并验证信息的来源。
- 为了生成这个签名,可以使用对称算法(例如 HMAC)或者非对称算法(例如 RSA)。
- 当使用 RSA 签名 JWT 时,一般会选择如 RS256(RSA 签名结合 SHA-256 散列算法)等算法。
- 签名过程:使用 RSA 私钥对 JWT 的头部和载荷进行签名。
- 验证过程:使用 RSA 公钥验证 JWT 的签名,确保数据的完整性和来源的真实性。
RSA加密算法生成密钥的方式
RSA 加密算法依赖一对密钥:公钥和私钥。准备公钥和私钥,主要有以下几种方式。
利用 RSA 工具类生成密钥
C# 中的 System.Security.Cryptography.RSA
类提供了 RSA 算法的实现,通过调用 RSA.Create()
方法,可以生成一个新的 RSA 对象,内含一对密钥。这是主要学习的方式,其他语言也可能有对应的RSA工具类
用命令行在系统下用其他工具生成密钥
一般是运维使用,了解即可
通过在线网站或工具生成密钥
在开发过程中,可以使用内置方法或在线工具生成密钥对。常见的在线工具包括:
PKCS#1 与 PKCS#8 标准
在使用 RSA 加密时,常会遇到两种密钥格式标准:PKCS#1 和 PKCS#8。它们各自有不同的用途和结构,下面来详细对比与优化这部分内容。
PKCS#1 (Public-Key Cryptography Standards #1)
PKCS#1 是最早由 RSA 实验室发布的 RSA 密钥标准,专门用于定义 RSA 密钥对的格式和相关的加密、签名机制。其主要特点包括:
- 公钥格式:只包含模数(n)和公用指数(e),这两个参数足以支持 RSA 加密过程。
- 私钥格式:不仅包含模数(n)和公用指数(e),还包括私有指数(d)、质数因子(p 和 q)以及其他辅助参数,这些信息共同确保私钥能够正确解密和签名。
- 专用性:PKCS#1 完全针对 RSA 密钥设计,只适用于存储和生成 RSA 密钥对。如果应用场景仅限于 RSA 加密,而不需要额外的密钥加密或跨算法支持,那么使用 PKCS#1 是个简洁高效的选择。
PKCS#8 (Public-Key Cryptography Standards #8)
PKCS#8 是一种更通用的密钥封装格式,能够存储任何类型的私钥,不仅仅局限于 RSA。其主要特点包括:
- 通用性:不仅可以封装 RSA 私钥,还可以用于其他加密算法(如椭圆曲线 EC),这使得 PKCS#8 适用于多算法环境。
- 双层封装:PKCS#8 私钥包含两部分——私钥算法标识符和私钥值。这样的结构允许在传输或存储过程中通过密码保护私钥,增强安全性。
- 应用场景:如果需要支持多种算法,或者要求对私钥进行加密保护,PKCS#8 是更为理想的选择。它在加密、封装和传输私钥方面提供了更高的灵活性和安全保障。
总结
- PKCS#1 适用于单纯的 RSA 密钥对生成和存储,其结构简单直接,适合不需要复杂保护措施的场景。
- PKCS#8 则是一个更通用的密钥封装格式,支持多种加密算法和密码保护,适用于需要更高安全性和灵活性的环境。
通过理解这两种标准的差异,开发者可以根据实际需求选择最合适的密钥格式,从而为系统构建稳固的安全基础。如果没有特殊需求的话,选择PKCS#1即可。
C# 中生成RSA密钥
利用 C# 中的 RSA 类,你可以轻松生成并导出密钥对。例如:
使用PKCS#1标准生成RSA密钥对
// -----------------------------
// 使用PKCS#1标准生成RSA密钥对
// -----------------------------
using (RSA rsa = RSA.Create())
{
// 设置密钥长度,通常为2048位或4096位,位数越高安全性越高,但运算性能越差
rsa.KeySize = 2048;
// 导出RSA公钥,使用PKCS#1格式,公钥只包含模数(n)和公用指数(e)
var exportRsaPublicKey = rsa.ExportRSAPublicKey();
// 导出RSA私钥,使用PKCS#1格式,私钥包含模数(n)、私有指数(d)、质数因子(p, q)等信息
var exportRsaPrivateKey = rsa.ExportRSAPrivateKey();
// 将导出的字节数组转换为Base64字符串,以便于查看和存储
var base64PublicString = Convert.ToBase64String(exportRsaPublicKey);
var base64PrivateString = Convert.ToBase64String(exportRsaPrivateKey);
// 输出Base64格式的公钥和私钥
Console.WriteLine($"Public key: {base64PublicString}");
Console.WriteLine($"Private key: {base64PrivateString}");
}
使用PKCS#8标准生成RSA密钥对
// -----------------------------
// 使用PKCS#8标准生成RSA密钥对
// -----------------------------
using (RSA rsa = RSA.Create())
{
// 同样设置密钥长度
rsa.KeySize = 2048;
// 导出公钥,使用PKCS#8标准的SubjectPublicKeyInfo格式
var exportRsaPublicKey = rsa.ExportSubjectPublicKeyInfo();
// 导出私钥,使用PKCS#8标准的格式,该格式更通用,可用于存储其他类型的私钥
var exportRsaPrivateKey = rsa.ExportPkcs8PrivateKey();
// 转换为Base64字符串便于展示和存储
var base64PublicString = Convert.ToBase64String(exportRsaPublicKey);
var base64PrivateString = Convert.ToBase64String(exportRsaPrivateKey);
// 输出使用PKCS#8格式的公钥和私钥
Console.WriteLine($"PKCS#8 Public key: {base64PublicString}");
Console.WriteLine($"PKCS#8 Private key: {base64PrivateString}");
}
使用PKCS#8标准生成加密的RSA私钥(私钥保护)
// -------------------------------------------------
// 使用PKCS#8标准生成加密的RSA私钥(私钥保护)
// -------------------------------------------------
using (RSA rsa = RSA.Create())
{
// 设置密钥长度,2048位为常用选择
rsa.KeySize = 2048;
// 设置私钥加密参数,使用AES256-CBC进行加密,散列算法为SHA256,迭代次数为10000次
var pbeParameters = new PbeParameters(
PbeEncryptionAlgorithm.Aes256Cbc, // 加密算法:AES256-CBC
HashAlgorithmName.SHA256, // 散列算法:SHA256
iterationCount: 10000 // 迭代次数:10000(增加破解难度)
);
// 定义一个用于保护私钥的密码,生产环境中应使用复杂且安全的密码
var password = "password";
// 导出公钥,使用PKCS#8的SubjectPublicKeyInfo格式
var exportRsaPublicKey = rsa.ExportSubjectPublicKeyInfo();
// 导出私钥,使用PKCS#8格式并通过指定密码及加密参数进行加密保护
var exportRsaPrivateKey = rsa.ExportEncryptedPkcs8PrivateKey(Encoding.UTF8.GetBytes(password), pbeParameters);
// 将导出的密钥转换为Base64字符串
var base64PublicString = Convert.ToBase64String(exportRsaPublicKey);
var base64PrivateString = Convert.ToBase64String(exportRsaPrivateKey);
// 输出加密后的PKCS#8格式公钥和受密码保护的私钥
Console.WriteLine($"PKCS#8 Public key: {base64PublicString}");
Console.WriteLine($"PKCS#8 Private key: {base64PrivateString}");
}
通过上述代码,你可以生成并查看 RSA 的公钥和私钥。不同的导出方法可用于导出 PKCS#1 或 PKCS#8 格式的密钥,根据需求选择适合的格式。
RSA的应用
数据加密场景
在数据加密中,发送方使用接收方的公钥对信息进行加密,确保只有持有相应私钥的接收方能够解密。这保证了数据在传输过程中的保密性。C#中Encrypt 方法可以实现加密,Decrypt 方法实现了解密。
例如,假设小明想给小英发送一条保密信息。小英生成了一对密钥,将公钥公开,而私钥自己保管。小明获取到小英的公钥后,用其加密信息并发送。即使其他人截获了这条加密信息,也无法解密,只有小英能用她的私钥解密读取内容。
数字签名场景
在数字签名中,发送方使用自己的私钥对信息或其摘要进行加密,生成签名。接收方使用发送方的公钥解密签名,验证信息的完整性和发送者的身份。游戏开发中的鉴权服和网关服就是这样的场景。
例如,小英想确认一份电子合同是由小明发送且未被篡改。小明用自己的私钥对合同内容的摘要进行加密,生成数字签名,并将其与合同一起发送给小英。小英收到后,用小明的公钥解密签名,得到摘要,并与合同内容计算出的摘要比对。如果一致,证明合同确实由小明发送且内容完整。
RSA注意事项
- 密钥长度:推荐使用至少 2048 位的密钥,4096 位则提供更高安全性,但性能开销也更大。
- 性能与应用场景:RSA 运算量较大,适合加密少量数据,如会话密钥的传输,而不适合大文件加密。
- 填充方式:常见的填充方式包括
RSAEncryptionPadding.Pkcs1
和更安全的RSAEncryptionPadding.OaepSHA256
,建议选择 OAEP 以增强安全性,OAEP 被认为比 PKCS#1 v1.5 更安全。
System.IdentityModel.Tokens.Jwt
简介
在 .NET 中,我们可以使用 System.IdentityModel.Tokens.Jwt
命名空间提供的类来创建、解析和验证 JSON Web Token(JWT)。本文将介绍 JwtPayload
、JwtSecurityToken
和 JwtSecurityTokenHandler
的用途,并通过示例演示如何创建不带签名的 JWT。
System.IdentityModel.Tokens.Jwt
命名空间提供了一组用于处理 JSON Web Tokens (JWT) 的类和方法,主要包括:
JwtPayload
:JWT 的有效负载部分,包含用户声明(claims)。JwtSecurityToken
:JWT 的整体结构,包括头部(Header)、有效负载(Payload)和签名(Signature)。JwtSecurityTokenHandler
:用于创建、解析和验证 JWT 的工具类。
JwtPayload
—— JWT 的有效负载部分
JwtPayload
介绍
JwtPayload
类代表 JWT 的有效负载部分(Payload),即存储用户身份信息和权限的部分。JWT 由三部分组成:
- Header(头部):描述 JWT 的类型和签名算法。
- Payload(有效负载):存储用户信息和声明(claims)。
- Signature(签名):用于验证 JWT 的完整性(本例中我们创建的是不带签名的 JWT)。
JwtPayload
主要用途
- 存储信息:
JwtPayload
主要用于存储用户信息、权限或其他相关数据。 - 声明(Claims):可以包含标准声明(如
iss
、sub
、exp
等)和自定义声明,以满足特定应用的需求。
JwtPayload
示例
以下代码创建了一个 JwtPayload
,其中包含用户的 ID、姓名、是否为管理员的标识,以及 JWT 的过期时间:
var payload = new JwtPayload
{
{ "sub", "1234567890" }, // 用户 ID
{ "name", "John Doe" }, // 用户姓名
{ "admin", true }, // 是否是管理员
{ "exp", new DateTimeOffset(DateTime.UtcNow.AddHours(1)).ToUnixTimeSeconds() } // 过期时间(Unix 时间戳)
};
JwtSecurityToken
—— JWT 的整体结构
JwtSecurityToken
介绍
JwtSecurityToken
类表示完整的 JWT,包括:
- 头部(Header)
- 有效负载(Payload)
- 签名(Signature,本文示例不涉及)
JwtSecurityToken
主要用途
- 创建和签名 JWT:使用
JwtSecurityToken
可以构建完整的 JWT,并对其进行签名,以确保其完整性和来源(本例不包含签名)。 - 解析 JWT:可以通过此类解析和读取 JWT 的内容。
JwtSecurityToken
示例
下面的代码创建了一个 JWT,其中:
issuer
是令牌的发布者。audience
是令牌的接收者。claims
是 JWT 载荷,包含用户信息。expires
设置 JWT 的过期时间。signingCredentials
(此示例用于展示,实际上创建不带签名的 JWT 时可省略)。
// 创建一个 JwtSecurityToken 对象
var token = new JwtSecurityToken(
issuer: "yourIssuer", // 设置令牌的发行者
audience: "yourAudience", // 设置令牌的接收者
claims: payload.Claims, // 包含的声明信息
expires: DateTime.UtcNow.AddHours(1), // 设置令牌的过期时间
signingCredentials: new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your_secret_key")), // 使用对称安全密钥
SecurityAlgorithms.HmacSha256) // 使用 HMAC SHA256 算法进行签名
);
JwtSecurityTokenHandler
—— JWT 处理工具
JwtSecurityTokenHandler
介绍
JwtSecurityTokenHandler
类用于创建、解析和验证 JWT,它提供了一组方法来处理 JWT 的整个生命周期。
JwtSecurityTokenHandler
主要用途
- 生成 JWT:使用
CreateToken
方法生成 JWT。 - 解析 JWT:使用
ReadToken
方法解析 JWT。 - 验证 JWT:使用
ValidateToken
方法验证 JWT 的有效性。 - 配置 JWT 验证参数:可配置验证参数,如签名验证、受众、发行者等。
JwtSecurityTokenHandler
示例
生成 JWT
我们可以使用 JwtSecurityTokenHandler.WriteToken()
方法将 JwtSecurityToken
转换为 JWT 字符串:
// 创建 JwtSecurityTokenHandler 实例,用于处理 JWT
var handler = new JwtSecurityTokenHandler();
// 将 JwtSecurityToken 对象序列化为紧凑格式的字符串
var jwtToken = handler.WriteToken(token);
// 输出生成的 JWT 字符串
Console.WriteLine("生成的JWT:" + jwtToken);
验证 JWT
验证 JWT 需要定义验证参数,例如:
- ValidateIssuer:是否验证
issuer
(令牌的发布者)。 - ValidateAudience:是否验证
audience
(令牌的接收者)。 - ValidateLifetime:是否验证 JWT 的过期时间。
- ValidateIssuerSigningKey:是否验证签名密钥(本示例未使用签名,但通常需要)。
// 定义 JWT 验证参数
var tokenValidationParameters = new TokenValidationParameters
{
// 是否验证发行者(Issuer)
ValidateIssuer = true,
// 是否验证接收方(Audience)
ValidateAudience = true,
// 是否验证令牌的生存期(Lifetime)
ValidateLifetime = true,
// 是否验证签名密钥(IssuerSigningKey)
ValidateIssuerSigningKey = true,
// 设置合法的发行者
ValidIssuer = "yourIssuer",
// 设置合法的接收方
ValidAudience = "yourAudience",
// 设置用于验证签名的密钥,示例中使用对称密钥;注意:本示例中未使用签名验证
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your_secret_key"))
};
// 声明一个变量用于存储验证后的安全令牌
SecurityToken validatedToken;
// 使用 handler 对 JWT 令牌进行验证,返回的 principal 包含用户的身份信息,
// 同时输出验证后的令牌到 validatedToken 变量中
var principal = handler.ValidateToken(jwtToken, tokenValidationParameters, out validatedToken);
// 输出验证成功的信息以及用户标识
Console.WriteLine("JWT 验证成功,用户信息:" + principal.Identity.Name);
总结
类名 | 作用 |
---|---|
JwtPayload |
用于存储和管理 JWT 的声明(claims)。 |
JwtSecurityToken |
代表整个 JWT,允许创建和解析 JWT。 |
JwtSecurityTokenHandler |
负责处理 JWT 的生成、解析和验证。 |
这三个类一起提供了完整的 JWT 处理能力,使开发人员能够方便地实现基于令牌的身份验证和授权。在本示例中,我们创建了一个不带签名的 JWT,适用于某些轻量级认证场景。但在实际应用中,建议始终使用签名 JWT 以增强安全性。
创建不带签名的 JWT
using System.IdentityModel.Tokens.Jwt;
namespace Main;
/// <summary>
/// 提供用于生成 JWT 的辅助方法。
/// </summary>
public static class JwtHelper
{
/// <summary>
/// 生成一个不带签名的 JWT。
/// </summary>
/// <param name="aId">用户的唯一标识符。</param>
/// <param name="username">用户名。</param>
/// <returns>生成的 JWT 字符串。</returns>
public static string GenerateJwtToken(int aId, string username)
{
// 创建 JWT 的有效载荷,包含用户的标识符和用户名,以及一个固定的 SId。
var jwtPayload = new JwtPayload()
{
{ "aId", aId },
{ "username", username },
{ "SId", "Tao" }
};
// 创建 JWT 安全令牌,设置发行者、受众、声明、过期时间等信息。
// 注意:此处未设置签名凭证(signingCredentials),生成的 JWT 将不包含签名。
var jwtSecurityToken = new JwtSecurityToken(
issuer: "Tao", // 令牌的发行者
audience: "Tao", // 令牌的受众
claims: jwtPayload.Claims, // 令牌的声明,即有效载荷
expires: DateTime.UtcNow.AddMilliseconds(3000), // 令牌的过期时间,当前时间后3秒
signingCredentials: null // 签名凭证,此处为 null,表示不签名
);
// 使用 JwtSecurityTokenHandler 将令牌对象序列化为字符串,并返回。
return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
}
}
Microsoft.IdentityModel.Tokens 简介
Microsoft.IdentityModel.Tokens 是一个 .NET 库,主要用于处理身份验证和授权相关的功能,特别适用于实现基于令牌(如 JWT)的身份验证应用程序。它的主要用途包括:
- 令牌生成与验证:生成 JSON Web Tokens (JWT),确保传递信息的安全性,并验证令牌的有效性与完整性。
- 签名算法:支持多种签名算法(如 HMAC、RSA、ECDSA),可根据需求选择合适的算法来签署和验证令牌。
- 密钥管理:提供对称或非对称密钥的加密与解密功能,方便密钥的管理。
- 声明与角色管理:允许在令牌中包含用户声明(如角色、权限等),以便在应用中实现基于角色的访问控制。
- 集成身份验证:可与 ASP.NET Core 及其他身份验证机制无缝集成,便于在 Web 应用和 API 中构建安全机制。
SigningCredentials —— 生成 JWT 时指定签名信息
用途
SigningCredentials 用于在生成 JWT 时指定签名信息,包括用于签名的密钥和算法。
主要属性
- Key:用于签名的密钥,通常是对称密钥或非对称密钥的一部分。
- Algorithm:指定签名时使用的算法,例如 HMAC SHA256 (HS256)、RS256、ES256 等。不同的算法会影响令牌的安全性和兼容性。
用法示例
// 将字符串 "your_secret_key_here" 转换为 UTF-8 字节数组,并利用该字节数组创建对称安全密钥。
// 该密钥用于生成和验证 JWT 的签名,确保令牌在传输过程中不会被篡改。
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your_secret_key_here"));
// 使用上面创建的对称安全密钥以及指定的签名算法 (HMAC SHA256),
// 创建用于签名 JWT 的签名凭据对象。
// 该对象将在生成 JWT 时用来对令牌进行签名,确保其安全性。
var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
TokenValidationParameters —— 验证 JWT 的各项参数
用途
TokenValidationParameters 用于定义在验证 JWT 时的各项参数,这些参数决定了如何检查令牌的有效性和安全性。
主要属性
- ValidateIssuer:指示是否验证令牌的发行者。
- ValidIssuer:指定被认为有效的发行者。
- ValidateAudience:指示是否验证令牌的接收方。
- ValidAudiences:指定一组有效的接收方。
- ValidateLifetime:指示是否验证令牌的生存期(例如 exp 声明)。
- IssuerSigningKey:用于验证令牌签名的密钥。
- RequireSignedTokens:指示是否要求令牌必须经过签名。
用法示例
// 创建 TokenValidationParameters 实例,用于定义验证 JWT 时的各项参数
var validationParameters = new TokenValidationParameters
{
// 指定在验证 JWT 时是否需要验证发行者(Issuer),此处设为 true 表示需要验证
ValidateIssuer = true,
// 设置有效的发行者名称,只有来自 "your_issuer" 的 JWT 才被认为是合法的
ValidIssuer = "your_issuer",
// 指定在验证 JWT 时是否需要验证接收方(Audience),此处设为 true 表示需要验证
ValidateAudience = true,
// 设置一组有效的接收方,只有在这些接收方中的 JWT 才会通过验证
ValidAudiences = new[] { "your_audience" },
// 指定在验证 JWT 时是否需要验证令牌的生命周期(如过期时间),此处设为 true 表示需要验证
ValidateLifetime = true,
// 设置用于验证 JWT 签名的安全密钥,此密钥必须与生成 JWT 时使用的密钥一致
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your_secret_key_here"))
};
总结
- SigningCredentials 负责创建和配置 JWT 的签名信息,确保令牌生成过程中的安全性。
- TokenValidationParameters 则在接收到 JWT 时用于验证令牌的有效性和安全性。
- 这两个类在实现基于令牌的身份验证与授权时至关重要,为应用程序的安全性和合规性提供了坚实的基础。
创建带签名的JWT并验证JWT令牌
使用PKCS#1标准生成RSA密钥对
Public key: MIIBCgKCAQEAyrv559IpjEX/rhhSCpzIzNy+JbpcaogbHClsymSA1OyGcqZZC4PAyLF8Aay6rkkk8Wq+o5MFAuPpNY0wDjRmRAacjQ8OWo8V00WBH2hREzfxyebcrDtY+KRgL+B1qoUtNIpDRWN+4VBMIpC93nEbjfRbwhE1FQNsPNQk4M1Yz+qdpcBtI8s69g6Tb0dcWmSIItMJovu2oTGZCXfibS9ewicZsEgmMEN8y08eXTGWTatOTR7++e1mhw+xI9e3brl8wjiWRUbRp2VPyGKdyIaUL6W4RuMIYoAhAltNuF1FPhmOyFw8Goa2LG3L4OqG7JI6RqFvwqWid32g/TlXwj9chQIDAQAB
Private key: MIIEowIBAAKCAQEAyrv559IpjEX/rhhSCpzIzNy+JbpcaogbHClsymSA1OyGcqZZC4PAyLF8Aay6rkkk8Wq+o5MFAuPpNY0wDjRmRAacjQ8OWo8V00WBH2hREzfxyebcrDtY+KRgL+B1qoUtNIpDRWN+4VBMIpC93nEbjfRbwhE1FQNsPNQk4M1Yz+qdpcBtI8s69g6Tb0dcWmSIItMJovu2oTGZCXfibS9ewicZsEgmMEN8y08eXTGWTatOTR7++e1mhw+xI9e3brl8wjiWRUbRp2VPyGKdyIaUL6W4RuMIYoAhAltNuF1FPhmOyFw8Goa2LG3L4OqG7JI6RqFvwqWid32g/TlXwj9chQIDAQABAoIBAD87nDnDIjRfTN0nNseamULUgvZPhy2vIA2LmiQSbuB6mYC/A70ErieqvAvlrSBZLg2ihq2MWJ7LKV0LmXaEv3TuPvv8OzThtiP8ZMbACbGd3Gtl92Q86oY69slYEDtMaIofqdXFr7hlDConzSDJVJfVJJ2GMnFIbQUYAhyfz9ihPcG0OVG2fluAv5NnBy7vah3VdE4ONUGYWjQfV/bLiqQLmUqCCeAV1yM0b3vSUq9dN8Fgch84YHAn0HC5V2nhfEofb97UdBMrAevQfELiLzgLwRLaakUP/kFq6piRJlN6CW/yQyOAQZaKWgwfOSTMTU9g1wPrLmovKJnCcPKX2S0CgYEA5+Y8uYy9nvE7H195QVKwgJdfl69hojtJ7uuL5g7BYXFjl0bDFbSIi59UPeZGE6NFsL2FmGr4V0LotTfCsA+lnBxMGcoexd3xsLqVdjDCqQJicPeoRPR6qgzWmYskZQ3nbI53zUgZpCQj6tDsfYYUIf8kteQdGPcPNwReVH7ANn8CgYEA383KuVbbtqtt7x6DV+izAHppT8SBepuP3/anF05RUSSNMHRshn52yNT4OcTVT9Kcuz9ecdJRGgeyTe8kBKNvH8koIy7E+Je0tOXR9n6mP5Wsqk2LlAiWX8joAVexmC5JpFyWTt00QtpW3x2G+BrK9wHj6upqgDEfhDKz4R+LEvsCgYEAw9bKXaCf2oS6e0ozqpjcehxcHEt+VnVfgKfnY4f+g/4wLucRhVkLH26UxBmeAKOYDSRgmFFuHLNitve6MNll8KptEjKBPnSq0acg5clT+0sKO4DK2EDm/okNCmpZeAX/9Mt0HSbQkK/8rSd3MKJ0iilgpUNZ12V9sea8ivrn9J0CgYBmTQZeo/J41HHC/Nqi4253SH09BiD9b4Bbqr6EK7uF8MRNItq8u7AGazswBOZQv3//I59DPhTuZhg+AZMgP6i7CdM8CdPlokrq6aaJGylZJUyw6BNmTmqWTBqpCnHWk0n8RIo8cGbELFPLVw7r+CtQr1+EcTTPdiTm49pPjICg4wKBgH8Nr0WY/LyN33vd5pOi1hlPxVzJTmB0l0hYf62r+iBpjdWEk95MjSRYyyfhw5AbHE/onCO0Ro173lhkpo7WvXtaMGz31kluT50hoRqml1ywRlQUbf/SRWozpTaBj6ikrNUQHmLY2DQ+gvtpSJqZQDWs6wFpXNFwvkf5yClJHp4X
基于秘钥生成签名
把生成的秘钥存起来,使用SigningCredentials生成签名。在GenerateJwtToken时把签名嵌入。
using DateTime = System.DateTime;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
Console.WriteLine(JwtHelper.GenerateJwtToken(1, "Tao Jwt"));
/// <summary>
/// 提供用于生成 JWT 的辅助方法。
/// </summary>
public static class JwtHelper
{
private const string PublicKeyPem =
"MIIBCgKCAQEAyrv559IpjEX/rhhSCpzIzNy+JbpcaogbHClsymSA1OyGcqZZC4PAyLF8Aay6rkkk8Wq+o5MFAuPpNY0wDjRmRAacjQ8OWo8V00WBH2hREzfxyebcrDtY+KRgL+B1qoUtNIpDRWN+4VBMIpC93nEbjfRbwhE1FQNsPNQk4M1Yz+qdpcBtI8s69g6Tb0dcWmSIItMJovu2oTGZCXfibS9ewicZsEgmMEN8y08eXTGWTatOTR7++e1mhw+xI9e3brl8wjiWRUbRp2VPyGKdyIaUL6W4RuMIYoAhAltNuF1FPhmOyFw8Goa2LG3L4OqG7JI6RqFvwqWid32g/TlXwj9chQIDAQAB";
private const string PrivateKeyPem =
"MIIEowIBAAKCAQEAyrv559IpjEX/rhhSCpzIzNy+JbpcaogbHClsymSA1OyGcqZZC4PAyLF8Aay6rkkk8Wq+o5MFAuPpNY0wDjRmRAacjQ8OWo8V00WBH2hREzfxyebcrDtY+KRgL+B1qoUtNIpDRWN+4VBMIpC93nEbjfRbwhE1FQNsPNQk4M1Yz+qdpcBtI8s69g6Tb0dcWmSIItMJovu2oTGZCXfibS9ewicZsEgmMEN8y08eXTGWTatOTR7++e1mhw+xI9e3brl8wjiWRUbRp2VPyGKdyIaUL6W4RuMIYoAhAltNuF1FPhmOyFw8Goa2LG3L4OqG7JI6RqFvwqWid32g/TlXwj9chQIDAQABAoIBAD87nDnDIjRfTN0nNseamULUgvZPhy2vIA2LmiQSbuB6mYC/A70ErieqvAvlrSBZLg2ihq2MWJ7LKV0LmXaEv3TuPvv8OzThtiP8ZMbACbGd3Gtl92Q86oY69slYEDtMaIofqdXFr7hlDConzSDJVJfVJJ2GMnFIbQUYAhyfz9ihPcG0OVG2fluAv5NnBy7vah3VdE4ONUGYWjQfV/bLiqQLmUqCCeAV1yM0b3vSUq9dN8Fgch84YHAn0HC5V2nhfEofb97UdBMrAevQfELiLzgLwRLaakUP/kFq6piRJlN6CW/yQyOAQZaKWgwfOSTMTU9g1wPrLmovKJnCcPKX2S0CgYEA5+Y8uYy9nvE7H195QVKwgJdfl69hojtJ7uuL5g7BYXFjl0bDFbSIi59UPeZGE6NFsL2FmGr4V0LotTfCsA+lnBxMGcoexd3xsLqVdjDCqQJicPeoRPR6qgzWmYskZQ3nbI53zUgZpCQj6tDsfYYUIf8kteQdGPcPNwReVH7ANn8CgYEA383KuVbbtqtt7x6DV+izAHppT8SBepuP3/anF05RUSSNMHRshn52yNT4OcTVT9Kcuz9ecdJRGgeyTe8kBKNvH8koIy7E+Je0tOXR9n6mP5Wsqk2LlAiWX8joAVexmC5JpFyWTt00QtpW3x2G+BrK9wHj6upqgDEfhDKz4R+LEvsCgYEAw9bKXaCf2oS6e0ozqpjcehxcHEt+VnVfgKfnY4f+g/4wLucRhVkLH26UxBmeAKOYDSRgmFFuHLNitve6MNll8KptEjKBPnSq0acg5clT+0sKO4DK2EDm/okNCmpZeAX/9Mt0HSbQkK/8rSd3MKJ0iilgpUNZ12V9sea8ivrn9J0CgYBmTQZeo/J41HHC/Nqi4253SH09BiD9b4Bbqr6EK7uF8MRNItq8u7AGazswBOZQv3//I59DPhTuZhg+AZMgP6i7CdM8CdPlokrq6aaJGylZJUyw6BNmTmqWTBqpCnHWk0n8RIo8cGbELFPLVw7r+CtQr1+EcTTPdiTm49pPjICg4wKBgH8Nr0WY/LyN33vd5pOi1hlPxVzJTmB0l0hYf62r+iBpjdWEk95MjSRYyyfhw5AbHE/onCO0Ro173lhkpo7WvXtaMGz31kluT50hoRqml1ywRlQUbf/SRWozpTaBj6ikrNUQHmLY2DQ+gvtpSJqZQDWs6wFpXNFwvkf5yClJHp4X";
private static readonly SigningCredentials _signingCredentials;
static JwtHelper()
{
// 创建一个 RSA 加密算法实例,用于后续的公钥和私钥操作
var rsa = RSA.Create();
// 从 Base64 编码的字符串中解码并导入 RSA 公钥
// out _ 表示忽略输出的字节数(成功导入的字节数)
rsa.ImportRSAPublicKey(source: Convert.FromBase64String(PublicKeyPem), out _);
// 从 Base64 编码的字符串中解码并导入 RSA 私钥
// 同样忽略输出的字节数
rsa.ImportRSAPrivateKey(source: Convert.FromBase64String(PrivateKeyPem), out _);
// 使用导入的 RSA 密钥创建 RsaSecurityKey,并指定 RSA SHA256 签名算法来生成 SigningCredentials 对象
// 该对象将用于 JWT 生成时对令牌进行签名
_signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);
}
/// <summary>
/// 生成一个带签名的 JWT。
/// </summary>
/// <param name="aId">用户的唯一标识符。</param>
/// <param name="username">用户名。</param>
/// <returns>生成的 JWT 字符串。</returns>
public static string GenerateJwtToken(int aId, string username)
{
// 创建 JWT 的有效载荷,包含用户的标识符和用户名,以及一个固定的 SId。
var jwtPayload = new JwtPayload()
{
{ "aId", aId },
{ "username", username },
{ "SId", "Tao" }
};
// 创建 JWT 安全令牌,设置发行者、受众、声明、过期时间等信息。
// 注意:此处未设置签名凭证(signingCredentials),生成的 JWT 将不包含签名。
var jwtSecurityToken = new JwtSecurityToken(
issuer: "Tao", // 令牌的发行者
audience: "Tao", // 令牌的受众
claims: jwtPayload.Claims, // 令牌的声明,即有效载荷
expires: DateTime.UtcNow.AddMilliseconds(3000), // 令牌的过期时间,当前时间后3秒
signingCredentials: _signingCredentials // 签名凭证,之前为 null,现在要赋值
);
// 使用 JwtSecurityTokenHandler 将令牌对象序列化为字符串,并返回。
return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
}
}
生成了带签名的JWT
这里仍然显示签名无效其实是因为密钥的格式没符合网站给的格式,但其实不影响使用
在JWTHelper中添加TokenValidationParameters对象和验证方法
/// <summary>
/// 提供用于生成 JWT 的辅助方法。
/// </summary>
public static class JwtHelper
{
//...
private static readonly SigningCredentials _signingCredentials;
private static readonly TokenValidationParameters _tokenValidationParameters;
static JwtHelper()
{
// 创建一个 RSA 加密算法实例,用于后续的公钥和私钥操作
var rsa = RSA.Create();
// 从 Base64 编码的字符串中解码并导入 RSA 公钥
// out _ 表示忽略输出的字节数(成功导入的字节数)
rsa.ImportRSAPublicKey(source: Convert.FromBase64String(PublicKeyPem), out _);
// 从 Base64 编码的字符串中解码并导入 RSA 私钥
// 同样忽略输出的字节数
rsa.ImportRSAPrivateKey(source: Convert.FromBase64String(PrivateKeyPem), out _);
// 使用导入的 RSA 密钥创建 RsaSecurityKey,并指定 RSA SHA256 签名算法来生成 SigningCredentials 对象
// 该对象将用于 JWT 生成时对令牌进行签名
_signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);
// 创建 TokenValidationParameters 对象,用于配置验证参数
// 如果改变有效的发行者或者有效的受众 让他和生成的token中的不匹配 验证时会报异常 比如改成小写的tao
_tokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = false, // 验证时间是否过期
ValidateIssuer = true, // 验证发行者
ValidateAudience = true, // 验证受众
ValidateIssuerSigningKey = true, // 验证签名密钥
ValidIssuer = "Tao", // 有效的发行者
ValidAudience = "Tao", // 有效的受众
IssuerSigningKey = new RsaSecurityKey(rsa) // RSA公钥作为签名密钥
};
}
/// <summary>
/// 生成一个带签名的 JWT。
/// </summary>
/// <param name="aId">用户的唯一标识符。</param>
/// <param name="username">用户名。</param>
/// <returns>生成的 JWT 字符串。</returns>
public static string GenerateJwtToken(int aId, string username)
{
// 创建 JWT 的有效载荷,包含用户的标识符和用户名,以及一个固定的 SId。
var jwtPayload = new JwtPayload()
{
{ "aId", aId },
{ "username", username },
{ "SId", "Tao" }
};
// 创建 JWT 安全令牌,设置发行者、受众、声明、过期时间等信息。
// 注意:此处未设置签名凭证(signingCredentials),生成的 JWT 将不包含签名。
var jwtSecurityToken = new JwtSecurityToken(
issuer: "Tao", // 令牌的发行者
audience: "Tao", // 令牌的受众
claims: jwtPayload.Claims, // 令牌的声明,即有效载荷
expires: DateTime.UtcNow.AddMilliseconds(3000), // 令牌的过期时间,当前时间后3秒
signingCredentials: _signingCredentials // 签名凭证,之前为 null,现在要赋值
);
// 使用 JwtSecurityTokenHandler 将令牌对象序列化为字符串,并返回。
return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
}
/// <summary>
/// 验证 JWT 令牌的有效性,并返回其负载(Payload)。
/// </summary>
/// <param name="token">待验证的 JWT 令牌字符串</param>
/// <param name="payload">输出参数,存放解析后的 JWT 负载</param>
/// <returns>若令牌验证成功返回 true,否则返回 false</returns>
public static bool ValidateToken(string token, out JwtPayload payload)
{
// 初始化输出的 payload 为 null
payload = null;
try
{
// 创建 JWT 安全令牌处理器实例,用于解析和验证 JWT
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
// 使用预设的 TokenValidationParameters 验证令牌
// out _ 表示忽略验证过程中返回的安全令牌对象
jwtSecurityTokenHandler.ValidateToken(token, _tokenValidationParameters, out _);
// 解析 JWT 令牌,并获取其负载部分(Payload),用于后续业务逻辑
payload = jwtSecurityTokenHandler.ReadJwtToken(token).Payload;
// 如果验证和解析过程均成功,则返回 true
return true;
}
// 捕获因受众(Audience)验证失败引起的异常
catch (SecurityTokenInvalidAudienceException)
{
Console.WriteLine("验证受众失败!");
return false;
}
// 捕获因发行者(Issuer)验证失败引起的异常
catch (SecurityTokenInvalidIssuerException)
{
Console.WriteLine("验证发行者失败!");
return false;
}
// 捕获其他异常,并输出异常信息后重新抛出异常
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
生成Token并进行验证
var generateJwtToken = JwtHelper.GenerateJwtToken(1, "Tao Jwt");
var isValidate = JwtHelper.ValidateToken(generateJwtToken,out var payload);
if (isValidate)
{
Console.WriteLine($"aId:{payload["aId"]} username:{payload["username"]} SId:{payload["SId"]}");
return;
}
Console.WriteLine(isValidate);
2.2 知识点代码
RSAHelper.cs
using System.Security.Cryptography;
using System.Text;
public class RSAHelper
{
/// <summary>
/// 生成使用 PKCS#1 标准的 RSA 密钥对
/// </summary>
/// <returns>
/// 返回一个元组,其中 Item1 为 Base64 编码的公钥,
/// Item2 为 Base64 编码的私钥(PKCS#1 格式)
/// </returns>
public static (string PublicKey, string PrivateKey) GeneratePkcs1KeyPair()
{
using (RSA rsa = RSA.Create())
{
// 设置密钥长度,通常为2048位或4096位,位数越高安全性越高,但运算性能越差
rsa.KeySize = 2048;
// 导出RSA公钥,使用PKCS#1格式,公钥只包含模数(n)和公用指数(e)
var exportRsaPublicKey = rsa.ExportRSAPublicKey();
// 导出RSA私钥,使用PKCS#1格式,私钥包含模数(n)、私有指数(d)、质数因子(p, q)等信息
var exportRsaPrivateKey = rsa.ExportRSAPrivateKey();
// 将导出的字节数组转换为Base64字符串,以便于查看和存储
var base64PublicString = Convert.ToBase64String(exportRsaPublicKey);
var base64PrivateString = Convert.ToBase64String(exportRsaPrivateKey);
// 输出Base64格式的公钥和私钥
Console.WriteLine($"Public key: {base64PublicString}");
Console.WriteLine($"Private key: {base64PrivateString}");
return (base64PublicString, base64PrivateString);
}
}
/// <summary>
/// 生成使用 PKCS#8 标准的 RSA 密钥对
/// </summary>
/// <returns>
/// 返回一个元组,其中 Item1 为 Base64 编码的公钥(SubjectPublicKeyInfo格式),
/// Item2 为 Base64 编码的私钥(PKCS#8 格式)
/// </returns>
public static (string PublicKey, string PrivateKey) GeneratePkcs8KeyPair()
{
using (RSA rsa = RSA.Create())
{
// 同样设置密钥长度
rsa.KeySize = 2048;
// 导出公钥,使用PKCS#8标准的SubjectPublicKeyInfo格式
var exportRsaPublicKey = rsa.ExportSubjectPublicKeyInfo();
// 导出私钥,使用PKCS#8标准的格式,该格式更通用,可用于存储其他类型的私钥
var exportRsaPrivateKey = rsa.ExportPkcs8PrivateKey();
// 转换为Base64字符串便于展示和存储
var base64PublicString = Convert.ToBase64String(exportRsaPublicKey);
var base64PrivateString = Convert.ToBase64String(exportRsaPrivateKey);
// 输出使用PKCS#8格式的公钥和私钥
Console.WriteLine($"PKCS#8 Public key: {base64PublicString}");
Console.WriteLine($"PKCS#8 Private key: {base64PrivateString}");
return (base64PublicString, base64PrivateString);
}
}
/// <summary>
/// 生成使用 PKCS#8 标准,并加密保护的 RSA 密钥对(私钥受密码保护)
/// </summary>
/// <param name="password">用于保护私钥的密码,建议使用复杂且安全的密码</param>
/// <returns>
/// 返回一个元组,其中 Item1 为 Base64 编码的公钥(SubjectPublicKeyInfo格式),
/// Item2 为 Base64 编码的经过密码保护的私钥(加密的 PKCS#8 格式)
/// </returns>
public static (string PublicKey, string EncryptedPrivateKey) GenerateEncryptedPkcs8KeyPair(string password)
{
using (RSA rsa = RSA.Create())
{
// 设置密钥长度,2048位为常用选择
rsa.KeySize = 2048;
// 设置私钥加密参数,使用AES256-CBC进行加密,散列算法为SHA256,迭代次数为10000次
var pbeParameters = new PbeParameters(
PbeEncryptionAlgorithm.Aes256Cbc, // 加密算法:AES256-CBC
HashAlgorithmName.SHA256, // 散列算法:SHA256
iterationCount: 10000 // 迭代次数:10000(增加破解难度)
);
// 外部传入一个用于保护私钥的密码password,生产环境中应使用复杂且安全的密码
// 导出公钥,使用PKCS#8的SubjectPublicKeyInfo格式
var exportRsaPublicKey = rsa.ExportSubjectPublicKeyInfo();
// 导出私钥,使用PKCS#8格式并通过指定密码及加密参数进行加密保护
var exportRsaPrivateKey =
rsa.ExportEncryptedPkcs8PrivateKey(Encoding.UTF8.GetBytes(password), pbeParameters);
// 将导出的密钥转换为Base64字符串
var base64PublicString = Convert.ToBase64String(exportRsaPublicKey);
var base64PrivateString = Convert.ToBase64String(exportRsaPrivateKey);
// 输出加密后的PKCS#8格式公钥和受密码保护的私钥
Console.WriteLine($"PKCS#8 Public key: {base64PublicString}");
Console.WriteLine($"PKCS#8 Private key: {base64PrivateString}");
return (base64PublicString, base64PrivateString);
}
}
}
JwtHelper.cs
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
namespace Main;
/// <summary>
/// 提供用于生成 JWT 的辅助方法。
/// </summary>
public static class JwtHelper
{
private const string PublicKeyPem =
"MIIBCgKCAQEAyrv559IpjEX/rhhSCpzIzNy+JbpcaogbHClsymSA1OyGcqZZC4PAyLF8Aay6rkkk8Wq+o5MFAuPpNY0wDjRmRAacjQ8OWo8V00WBH2hREzfxyebcrDtY+KRgL+B1qoUtNIpDRWN+4VBMIpC93nEbjfRbwhE1FQNsPNQk4M1Yz+qdpcBtI8s69g6Tb0dcWmSIItMJovu2oTGZCXfibS9ewicZsEgmMEN8y08eXTGWTatOTR7++e1mhw+xI9e3brl8wjiWRUbRp2VPyGKdyIaUL6W4RuMIYoAhAltNuF1FPhmOyFw8Goa2LG3L4OqG7JI6RqFvwqWid32g/TlXwj9chQIDAQAB";
private const string PrivateKeyPem =
"MIIEowIBAAKCAQEAyrv559IpjEX/rhhSCpzIzNy+JbpcaogbHClsymSA1OyGcqZZC4PAyLF8Aay6rkkk8Wq+o5MFAuPpNY0wDjRmRAacjQ8OWo8V00WBH2hREzfxyebcrDtY+KRgL+B1qoUtNIpDRWN+4VBMIpC93nEbjfRbwhE1FQNsPNQk4M1Yz+qdpcBtI8s69g6Tb0dcWmSIItMJovu2oTGZCXfibS9ewicZsEgmMEN8y08eXTGWTatOTR7++e1mhw+xI9e3brl8wjiWRUbRp2VPyGKdyIaUL6W4RuMIYoAhAltNuF1FPhmOyFw8Goa2LG3L4OqG7JI6RqFvwqWid32g/TlXwj9chQIDAQABAoIBAD87nDnDIjRfTN0nNseamULUgvZPhy2vIA2LmiQSbuB6mYC/A70ErieqvAvlrSBZLg2ihq2MWJ7LKV0LmXaEv3TuPvv8OzThtiP8ZMbACbGd3Gtl92Q86oY69slYEDtMaIofqdXFr7hlDConzSDJVJfVJJ2GMnFIbQUYAhyfz9ihPcG0OVG2fluAv5NnBy7vah3VdE4ONUGYWjQfV/bLiqQLmUqCCeAV1yM0b3vSUq9dN8Fgch84YHAn0HC5V2nhfEofb97UdBMrAevQfELiLzgLwRLaakUP/kFq6piRJlN6CW/yQyOAQZaKWgwfOSTMTU9g1wPrLmovKJnCcPKX2S0CgYEA5+Y8uYy9nvE7H195QVKwgJdfl69hojtJ7uuL5g7BYXFjl0bDFbSIi59UPeZGE6NFsL2FmGr4V0LotTfCsA+lnBxMGcoexd3xsLqVdjDCqQJicPeoRPR6qgzWmYskZQ3nbI53zUgZpCQj6tDsfYYUIf8kteQdGPcPNwReVH7ANn8CgYEA383KuVbbtqtt7x6DV+izAHppT8SBepuP3/anF05RUSSNMHRshn52yNT4OcTVT9Kcuz9ecdJRGgeyTe8kBKNvH8koIy7E+Je0tOXR9n6mP5Wsqk2LlAiWX8joAVexmC5JpFyWTt00QtpW3x2G+BrK9wHj6upqgDEfhDKz4R+LEvsCgYEAw9bKXaCf2oS6e0ozqpjcehxcHEt+VnVfgKfnY4f+g/4wLucRhVkLH26UxBmeAKOYDSRgmFFuHLNitve6MNll8KptEjKBPnSq0acg5clT+0sKO4DK2EDm/okNCmpZeAX/9Mt0HSbQkK/8rSd3MKJ0iilgpUNZ12V9sea8ivrn9J0CgYBmTQZeo/J41HHC/Nqi4253SH09BiD9b4Bbqr6EK7uF8MRNItq8u7AGazswBOZQv3//I59DPhTuZhg+AZMgP6i7CdM8CdPlokrq6aaJGylZJUyw6BNmTmqWTBqpCnHWk0n8RIo8cGbELFPLVw7r+CtQr1+EcTTPdiTm49pPjICg4wKBgH8Nr0WY/LyN33vd5pOi1hlPxVzJTmB0l0hYf62r+iBpjdWEk95MjSRYyyfhw5AbHE/onCO0Ro173lhkpo7WvXtaMGz31kluT50hoRqml1ywRlQUbf/SRWozpTaBj6ikrNUQHmLY2DQ+gvtpSJqZQDWs6wFpXNFwvkf5yClJHp4X";
private static readonly SigningCredentials _signingCredentials;
private static readonly TokenValidationParameters _tokenValidationParameters;
static JwtHelper()
{
// 创建一个 RSA 加密算法实例,用于后续的公钥和私钥操作
var rsa = RSA.Create();
// 从 Base64 编码的字符串中解码并导入 RSA 公钥
// out _ 表示忽略输出的字节数(成功导入的字节数)
rsa.ImportRSAPublicKey(source: Convert.FromBase64String(PublicKeyPem), out _);
// 从 Base64 编码的字符串中解码并导入 RSA 私钥
// 同样忽略输出的字节数
rsa.ImportRSAPrivateKey(source: Convert.FromBase64String(PrivateKeyPem), out _);
// 使用导入的 RSA 密钥创建 RsaSecurityKey,并指定 RSA SHA256 签名算法来生成 SigningCredentials 对象
// 该对象将用于 JWT 生成时对令牌进行签名
_signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);
// 创建 TokenValidationParameters 对象,用于配置验证参数
// 如果改变有效的发行者或者有效的受众 让他和生成的token中的不匹配 验证时会报异常 比如改成小写的tao
_tokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = false, // 验证时间是否过期
ValidateIssuer = true, // 验证发行者
ValidateAudience = true, // 验证受众
ValidateIssuerSigningKey = true, // 验证签名密钥
ValidIssuer = "Tao", // 有效的发行者
ValidAudience = "Tao", // 有效的受众
IssuerSigningKey = new RsaSecurityKey(rsa) // RSA公钥作为签名密钥
};
}
/// <summary>
/// 生成一个带签名的 JWT。
/// </summary>
/// <param name="aId">用户的唯一标识符。</param>
/// <param name="username">用户名。</param>
/// <returns>生成的 JWT 字符串。</returns>
public static string GenerateJwtToken(int aId, string username)
{
// 创建 JWT 的有效载荷,包含用户的标识符和用户名,以及一个固定的 SId。
var jwtPayload = new JwtPayload()
{
{ "aId", aId },
{ "username", username },
{ "SId", "Tao" }
};
// 创建 JWT 安全令牌,设置发行者、受众、声明、过期时间等信息。
// 注意:此处未设置签名凭证(signingCredentials),生成的 JWT 将不包含签名。
var jwtSecurityToken = new JwtSecurityToken(
issuer: "Tao", // 令牌的发行者
audience: "Tao", // 令牌的受众
claims: jwtPayload.Claims, // 令牌的声明,即有效载荷
expires: DateTime.UtcNow.AddMilliseconds(3000), // 令牌的过期时间,当前时间后3秒
signingCredentials: _signingCredentials // 签名凭证,之前为 null,现在要赋值
);
// 使用 JwtSecurityTokenHandler 将令牌对象序列化为字符串,并返回。
return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
}
/// <summary>
/// 验证 JWT 令牌的有效性,并返回其负载(Payload)。
/// </summary>
/// <param name="token">待验证的 JWT 令牌字符串</param>
/// <param name="payload">输出参数,存放解析后的 JWT 负载</param>
/// <returns>若令牌验证成功返回 true,否则返回 false</returns>
public static bool ValidateToken(string token, out JwtPayload payload)
{
// 初始化输出的 payload 为 null
payload = null;
try
{
// 创建 JWT 安全令牌处理器实例,用于解析和验证 JWT
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
// 使用预设的 TokenValidationParameters 验证令牌
// out _ 表示忽略验证过程中返回的安全令牌对象
jwtSecurityTokenHandler.ValidateToken(token, _tokenValidationParameters, out _);
// 解析 JWT 令牌,并获取其负载部分(Payload),用于后续业务逻辑
payload = jwtSecurityTokenHandler.ReadJwtToken(token).Payload;
// 如果验证和解析过程均成功,则返回 true
return true;
}
// 捕获因受众(Audience)验证失败引起的异常
catch (SecurityTokenInvalidAudienceException)
{
Console.WriteLine("验证受众失败!");
return false;
}
// 捕获因发行者(Issuer)验证失败引起的异常
catch (SecurityTokenInvalidIssuerException)
{
Console.WriteLine("验证发行者失败!");
return false;
}
// 捕获其他异常,并输出异常信息后重新抛出异常
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
Program.cs
using Main;
// Console.WriteLine(JwtHelper.GenerateJwtToken(1, "Tao Jwt"));
var generateJwtToken = JwtHelper.GenerateJwtToken(1, "Tao Jwt");
Console.WriteLine(generateJwtToken);
var isValidate = JwtHelper.ValidateToken(generateJwtToken,out var payload);
if (isValidate)
{
Console.WriteLine($"aId:{payload["aId"]} username:{payload["username"]} SId:{payload["SId"]}");
return;
}
Console.WriteLine(isValidate);
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com