2.JWT与RSA的原理、实现及应用

  1. 2.JWT
    1. 2.1 知识点
      1. JWT
        1. 什么是JWT?
        2. JWT的核心特性
        3. 为什么选择JWT?
        4. 何时使用JWT?
        5. JWT的签名验证
          1. JWT的结构
          2. JWT的信任问题
          3. JWT的加密算法
          4. JWT的加密过程详解
          5. 总结
      2. RSA
        1. RSA是什么
        2. RSA和JWT的关系
        3. RSA加密算法生成密钥的方式
          1. 利用 RSA 工具类生成密钥
          2. 用命令行在系统下用其他工具生成密钥
          3. 通过在线网站或工具生成密钥
        4. PKCS#1 与 PKCS#8 标准
          1. PKCS#1 (Public-Key Cryptography Standards #1)
          2. PKCS#8 (Public-Key Cryptography Standards #8)
          3. 总结
        5. C# 中生成RSA密钥
          1. 使用PKCS#1标准生成RSA密钥对
          2. 使用PKCS#8标准生成RSA密钥对
          3. 使用PKCS#8标准生成加密的RSA私钥(私钥保护)
        6. RSA的应用
          1. 数据加密场景
          2. 数字签名场景
        7. RSA注意事项
      3. System.IdentityModel.Tokens.Jwt 简介
        1. JwtPayload —— JWT 的有效负载部分
          1. JwtPayload 介绍
          2. JwtPayload 主要用途
          3. JwtPayload 示例
        2. JwtSecurityToken —— JWT 的整体结构
          1. JwtSecurityToken 介绍
          2. JwtSecurityToken 主要用途
          3. JwtSecurityToken 示例
        3. JwtSecurityTokenHandler —— JWT 处理工具
          1. JwtSecurityTokenHandler 介绍
          2. JwtSecurityTokenHandler 主要用途
          3. JwtSecurityTokenHandler 示例
            1. 生成 JWT
            2. 验证 JWT
        4. 总结
      4. 创建不带签名的 JWT
      5. Microsoft.IdentityModel.Tokens 简介
        1. SigningCredentials —— 生成 JWT 时指定签名信息
          1. 用途
          2. 主要属性
          3. 用法示例
        2. TokenValidationParameters —— 验证 JWT 的各项参数
          1. 用途
          2. 主要属性
          3. 用法示例
        3. 总结
      6. 创建带签名的JWT并验证JWT令牌
        1. 使用PKCS#1标准生成RSA密钥对
        2. 基于秘钥生成签名
        3. 生成了带签名的JWT
        4. 在JWTHelper中添加TokenValidationParameters对象和验证方法
        5. 生成Token并进行验证
    2. 2.2 知识点代码
      1. RSAHelper.cs
      2. JwtHelper.cs
      3. Program.cs

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(使用其他加密算法的步骤类似)为例说明:

  1. 编码Header和Payload

    • 对JWT的头部和载荷部分分别进行Base64Url编码:
      var encodedHeader = base64UrlEncode(header);
      var encodedPayload = base64UrlEncode(payload);
      var headerAndPayload = encodedHeader + "." + encodedPayload;
      
    • 这里的headerAndPayload即为JWT的前两部分,用于后续的签名计算。
  2. 生成签名

    • 根据所选择的加密算法(如HMACSHA256),对headerAndPayload进行签名计算,生成固定长度的签名字符串:
      var signature = HMACSHA256(headerAndPayload);
      
    • 这个过程确保只有知道密钥或私钥的一方才能生成有效签名,从而防止数据被篡改。
  3. 构造最终JWT

    • 将编码后的Header和Payload与签名组合,形成完整的JWT:
      var jwt = headerAndPayload + "." + signature;
      
    • 这个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)。本文将介绍 JwtPayloadJwtSecurityTokenJwtSecurityTokenHandler 的用途,并通过示例演示如何创建不带签名的 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 由三部分组成:

  1. Header(头部):描述 JWT 的类型和签名算法。
  2. Payload(有效负载):存储用户信息和声明(claims)。
  3. Signature(签名):用于验证 JWT 的完整性(本例中我们创建的是不带签名的 JWT)。
JwtPayload 主要用途
  • 存储信息JwtPayload 主要用于存储用户信息、权限或其他相关数据。
  • 声明(Claims):可以包含标准声明(如 isssubexp 等)和自定义声明,以满足特定应用的需求。
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

×

喜欢就点赞,疼爱就打赏