菜单栏

在 .NET Core 中生成和解析JWT令牌(3):使用 RS256(RSA-SHA256)算法签名

@iEricLee Creation Time : 2024-09-11 11:48:40 Last Modification Time : 2024-09-11 12:53:11

在前面我们已经实现 在 .NET Core 中生成和解析JWT令牌(2):使用 HS256(HMAC-SHA256)算法签名,本文中将通过示例演示使用 RS256(RSA-SHA256)算法签名创建JWT令牌

思路和流程是相同的,不同的地方在于,密钥的生成和解析不一样。

使用 RS256(RSA-SHA256)签名算法生成JWT令牌

为了方便代码管理,将这个示例的核心代码封装在一个单独的类 Jwt_RSASHA256Helper 中。

假设我们已经生成私钥和公钥。通过私钥生成令牌:(密钥部分核心代码)

//私钥
private static string PRIVATE_KEY = "PRIVATE_KEY";
//公钥
private static string PUBLIC_KEY = "PUBLIC_KEY";

var rsa = RSA.Create()
             .ImportPkcs8PrivateKey(Convert.FromBase64String(PRIVATE_KEY), out _);
SecurityKey securityKey = new RsaSecurityKey(rsa);

var token = new JwtSecurityToken(
    issuer: "fan",
    audience: "fan",
    claims: claims,
    notBefore: DateTime.Now,
    expires: DateTime.Now.AddSeconds(10),
    signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha256)
);

签名凭据 SigningCredentials 构造函数接收两个参数:密钥和算法,这里使用对称密钥 RsaSecurityKeySecurityAlgorithms.RsaSha256 算法。

手动校验和解析令牌

RSA签名算法的校验需要公钥和私钥,验证参数和 HS256 略有不同。

登陆 https://jwt.io ,将生成的访问令牌复制到输入框:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IlVYR1pYUF9HSFBFNlUwLVhXUVFZQlMxUUJCWlAzT1cxQlZCVTZTUVEiLCJ0eXAiOiJhdCtqd3QifQ.eyJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo3MDE4LyIsImV4cCI6MTcyNDUwODE5MCwiaWF0IjoxNzI0NTA0NTkwLCJzY29wZSI6Im9mZmxpbmVfYWNjZXNzIiwianRpIjoiYjI2NTNkZTktZDNhZi00M2Y0LTgwOWMtZWIyOTU2ODQ4NjBkIiwic3ViIjoiYWRtaW4iLCJzb21lIGNsYWltIjoic29tZSB2YWx1ZSIsIm9pX3Byc3QiOiJjbGllbnRfYXBwIiwib2lfYXVfaWQiOiJiMmEzOTVjNi1hMGY1LTQ5OGQtYTcwMy1mMmQ2MWM4Nzk1ZmUiLCJjbGllbnRfaWQiOiJjbGllbnRfYXBwIiwib2lfdGtuX2lkIjoiMTEzYzY1NjAtNjc4OC00MWI5LTk2OWUtZmNlODcxYjgzYTk3In0.OImRQf6qBaVn_qe9ZtuQKu3TSEKpmWLmqkv0uwrPqe0vkGAa26zvl3J4hfbcKM-XYB1EsamIfISRudbc8PoQ5ZBoXEbWU1A6eNMqg53MZ1UZedNZ41Vy_1Z_rmZqMGwdacMt4zxgLR0PN551V-FEif_CPCDYWaMHRB9ghjKVrn2CplMl73ssZy0D61Qblb1utb-NXHTsNzGWvfgDZ39-BINBjqg06Nu4bccfJUbtIfxp18CYxGfPMI60JQOVyVKC7APtrqbLIdCkgYG_gS3OOjOm9cDSmDgblrBAkvzF5xWhMZmkt1dSMqyt8rnKlSb_YO5RywnFxvvnmjnrPk-bc7ldF6rjHqN6drxqJ7PzKLr-CqmWFgYC3h6N26BU7hItuzHUu4HyViuVZZbi78EKiQmXUmzF4AZMfhEH-vCy23XDi-6klnrx6lN2tv1ath1wDhWGHL7NavvTNyn9yXN-97Dzv4ujJwo1CpFzqgImX84m5sHe2o12JS1Kk-5gHP5VPvuFgRKirP9_ArnjXxOL8NFuT1ji_CffDfrEY9bicdd593NcA6Ljjujv-iK_Stjcku1RpDJtnW6Ap-LRYsjdgkxx9_wqv9H3BJiyyNYXLQmpx9f3XlnmJKFWhgDGHogLcZHR__EgLWprmJcm2BTL9VORHzG-E70WRBIKlAD4B5g"

只需要粘贴 access_token 内容即可。

首先获取公钥,访问 https://localhost:7018/.well-known/jwks 返回如下内容:

关于 JWKS ,可以参看 JWS和JWKS详解

{
  "keys": [
    {
      "kid": "UXGZXP_GHPE6U0-XWQQYBS1QBBZP3OW1BVBU6SQQ",
      "use": "sig",
      "kty": "RSA",
      "alg": "RS256",
      "e": "AQAB",
      "n": "uXGzXP_GHpe6U0-xWQqyBs1qBBzP3OW1bVBU6sQqKU1Ef5IjK8Qoej4d7mjYQk5keq9OOu2d4WnVp5SiqqJQwCOY74dBFa5JhAGvlW1gk3hewtwtGFtq0LfLtlTzs1QFeqX-IWZoKksLfK6yZn6w1Ol1tv2UYeHuTLwM0zcaEqyHwUl1-B2gc86zu2evv9TxQWZgIEemrHJu7QQL59Z4BRE15v7kHUc_mLq9uYFT-a2Ftq7mcQWgfIphTGs4SZzKV0vRLwWsERSwyfrs4YyDL0NvqswWq3NNJF0ascc3_TUor-rjHi98L-Shi-Ga1ItMCKXbjnVcAXSwX47c8gL5qPZkl0r9Csbp3vK0Ukc91ZvkTYZ7IDCbqHU7db7QsdtVyz92vJu45cJbGuwDGqwqP0ExYKbf5zAVXv25Gd3aBLRt_s7sQOOWWNn1XbIdtpOdiD124ZiY1tA7J-BqHV4EWlu0azu6lS7q1GSfMPiK_i6ltB1H7xVaW9Mw9PxHr_ZtO_mZ1B4XDeoMSCWv1wgSzk-U03AB_qF8Z4OeIZhhPZ4fqCfhQKEfT7sZInCcFzhFr3gMlWN5-THcFBlcLi-fntnsMEYYXBw8UKB3OZV0u8ZJ0xp6_yj-45lRgBU6gVCW81elhgdE65LBUlsEy-UI0Eeb2Hq73mdGpgzQ_Z1w0Wc"
    }
  ]
}

可能会返回多个公钥,这里我们选择:

{
    "kid": "UXGZXP_GHPE6U0-XWQQYBS1QBBZP3OW1BVBU6SQQ",
    "use": "sig",
    "kty": "RSA",
    "alg": "RS256",
    "e": "AQAB",
    "n": "uXGzXP_GHpe6U0-xWQqyBs1qBBzP3OW1bVBU6sQqKU1Ef5IjK8Qoej4d7mjYQk5keq9OOu2d4WnVp5SiqqJQwCOY74dBFa5JhAGvlW1gk3hewtwtGFtq0LfLtlTzs1QFeqX-IWZoKksLfK6yZn6w1Ol1tv2UYeHuTLwM0zcaEqyHwUl1-B2gc86zu2evv9TxQWZgIEemrHJu7QQL59Z4BRE15v7kHUc_mLq9uYFT-a2Ftq7mcQWgfIphTGs4SZzKV0vRLwWsERSwyfrs4YyDL0NvqswWq3NNJF0ascc3_TUor-rjHi98L-Shi-Ga1ItMCKXbjnVcAXSwX47c8gL5qPZkl0r9Csbp3vK0Ukc91ZvkTYZ7IDCbqHU7db7QsdtVyz92vJu45cJbGuwDGqwqP0ExYKbf5zAVXv25Gd3aBLRt_s7sQOOWWNn1XbIdtpOdiD124ZiY1tA7J-BqHV4EWlu0azu6lS7q1GSfMPiK_i6ltB1H7xVaW9Mw9PxHr_ZtO_mZ1B4XDeoMSCWv1wgSzk-U03AB_qF8Z4OeIZhhPZ4fqCfhQKEfT7sZInCcFzhFr3gMlWN5-THcFBlcLi-fntnsMEYYXBw8UKB3OZV0u8ZJ0xp6_yj-45lRgBU6gVCW81elhgdE65LBUlsEy-UI0Eeb2Hq73mdGpgzQ_Z1w0Wc"
}

复制到公钥输入框中。将私钥复制到私钥输入框中:(代码校验不需要知道私钥)

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

验证成功,如下图所示:

jwt_rsa256

代码校验和解析令牌

通过公钥创建 RsaSecurityKey,构建令牌验证参数。注意需要对密钥进行格式转换。

//验证
public static bool VerifyJwtToken(string token, out ClaimsPrincipal principal)
{
    principal = null;
    //校验token
    var validateParameter = new TokenValidationParameters()
    {
        ValidateLifetime = true,
        ValidateAudience = true,
        ValidateIssuer = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = Issuer,
        ValidAudience = Audience,
        IssuerSigningKey = new RsaSecurityKey(CreateRsaProviderFromPublicKey(PUBLIC_KEY)),

        ClockSkew = TimeSpan.Zero//校验过期时间必须加此属性
    };
    bool success = false;
    try
    {
        //校验并解析token,validatedToken是解密后的对象
        principal = new JwtSecurityTokenHandler().ValidateToken(token, validateParameter, out SecurityToken validatedToken);
        //获取payload中的数据 
        //var jwtPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson(); 
        success = true;

    }
    catch (SecurityTokenExpiredException ex)
    {
        //表示过期
        success = false;
    }
    catch (SecurityTokenException ex)
    {
        //表示token错误
        success = false;
    }
    catch (Exception ex)
    {
        success = false;
    }
    return success;
}

#region 私有方法
/// <summary>
/// 通过公钥创建RSA
/// </summary>
/// <param name="publicKeyString"></param>
/// <returns></returns>
private static RSA CreateRsaProviderFromPublicKey(string publicKeyString)
{
    // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
    byte[] seqOid = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
    byte[] seq = new byte[15];
    var x509Key = Convert.FromBase64String(publicKeyString);
    // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
    using (MemoryStream mem = new MemoryStream(x509Key))
    {
        using (BinaryReader binr = new BinaryReader(mem))  //wrap Memory Stream with BinaryReader for easy reading
        {
            byte bt = 0;
            ushort twobytes = 0;

            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                binr.ReadByte();    //advance 1 byte
            else if (twobytes == 0x8230)
                binr.ReadInt16();   //advance 2 bytes
            else
                return null;

            seq = binr.ReadBytes(15);       //read the Sequence OID
            if (!CompareBytearrays(seq, seqOid))    //make sure Sequence for OID is correct
                return null;

            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
                binr.ReadByte();    //advance 1 byte
            else if (twobytes == 0x8203)
                binr.ReadInt16();   //advance 2 bytes
            else
                return null;

            bt = binr.ReadByte();
            if (bt != 0x00)     //expect null byte next
                return null;

            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                binr.ReadByte();    //advance 1 byte
            else if (twobytes == 0x8230)
                binr.ReadInt16();   //advance 2 bytes
            else
                return null;

            twobytes = binr.ReadUInt16();
            byte lowbyte = 0x00;
            byte highbyte = 0x00;

            if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
                lowbyte = binr.ReadByte();  // read next bytes which is bytes in modulus
            else if (twobytes == 0x8202)
            {
                highbyte = binr.ReadByte(); //advance 2 bytes
                lowbyte = binr.ReadByte();
            }
            else
                return null;
            byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };   //reverse byte order since asn.1 key uses big endian order
            int modsize = BitConverter.ToInt32(modint, 0);

            int firstbyte = binr.PeekChar();
            if (firstbyte == 0x00)
            {   //if first byte (highest order) of modulus is zero, don't include it
                binr.ReadByte();    //skip this null byte
                modsize -= 1;   //reduce modulus buffer size by 1
            }

            byte[] modulus = binr.ReadBytes(modsize);   //read the modulus bytes

            if (binr.ReadByte() != 0x02)            //expect an Integer for the exponent data
                return null;
            int expbytes = (int)binr.ReadByte();        // should only need one byte for actual exponent data (for all useful values)
            byte[] exponent = binr.ReadBytes(expbytes);

            // ------- create RSACryptoServiceProvider instance and initialize with public key -----
            var rsa = RSA.Create();
            RSAParameters rsaKeyInfo = new RSAParameters
            {
                Modulus = modulus,
                Exponent = exponent
            };
            rsa.ImportParameters(rsaKeyInfo);

            return rsa;
        }

    }
}
private static bool CompareBytearrays(byte[] a, byte[] b)
{
    if (a.Length != b.Length)
        return false;
    int i = 0;
    foreach (byte c in a)
    {
        if (c != b[i])
            return false;
        i++;
    }
    return true;
}
#endregion

在 Program.cs 中调用:

string rsa_token = Jwt_RSAHelper.GetJwtToken();
bool success = Jwt_RSAHelper.VerifyJwtToken(rsa_token, out ClaimsPrincipal cp);
if(success)
{
    Console.WriteLine("RSA token Success");
}

参考文章


JWT
In This Document
Copyright © 2024 知识乐 湘ICP备2022022129号-1