c# RSA 验证 JWT Token 笔记
从认证服务器获取公匙
从http://localhost:9000/oauth2/jwks获取公匙,返回数据如下:
1 { 2 "keys":[ 3 { 4 "kty":"RSA", // 使用RSA算法 5 "use":"sig", // 类型为签名 6 "kid":"03ece87289fe54eee377a1380b0e49a2", // 与token有关 7 "e":"AQAB", // RSA算法中的e,base64url(注意与base64的区别) 8 "n":"3JjRAiTwPOCDrWFcsHDovYDuBaqWuSSLxSp27Bc6rDnsepkw-yISVRlM4C-MwgL4wiDZnnQBv9-zZi8on0_L1L7UuuN5HFvvd01xApIWGdhJwQl2ijltwOKG8osxQQXtkDLOPOxhV-Fs5K9KQWW0AXdkPUXYulP3854MXmNWOqtC3fhMg78MUZAAKGrj67qk0tRdtxXIgF_Mqn5cMsEq8X7xWcbcPwWuc_NsfSxs43Y-p-7THMrv8vRzIQFUO27WGMZP9cTlXx0_OuhRaNnZUJoamp89-j-XMBfYzsCFyD_a3dBdUMvNtvgDq4IylA1oyT1F_J8xi4SPHwtNvnWxLQ", // RSA算法中的n,base64url(注意与base64的区别) 9 "alg":"RS256" // 哈希算法名称,使用“SHA256”哈希算法。 10 } 11 ] 12 }
从认证服务器获取共享Token
eyJhbGciOiJSUzI1NiIsImtpZCI6IjAzZWNlODcyODlmZTU0ZWVlMzc3YTEzODBiMGU0OWEyIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1NDMxOTkxNTksImV4cCI6MTU0MzIwMjc1OSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9yZXNvdXJjZXMiLCJhcGkxIl0sImNsaWVudF9pZCI6Im12YyIsInN1YiI6IjQxMjciLCJhdXRoX3RpbWUiOjE1NDMxOTkxNTIsImlkcCI6ImxvY2FsIiwibmFtZSI6IjE4ODg4ODg4ODg3Iiwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSIsImFwaTEiLCJvZmZsaW5lX2FjY2VzcyJdLCJhbXIiOlsicHdkIl19.vGn_PiK_aomcVfGBGX0QH6-1ySd5Viv4ZHDA1s2xZ5HstgS07WawQpxbSG83aLAig_NpKiTR3aNZKTQ40s4PgUJLJ7x1WTaJUxJApWYi9oLsPxK6p7sxi3OPPHGWIKpka4ht_p_9SY13oToWvBECkhU6F2Oeo3iEKWmGy4n3aOLHuQe4rNEVrKnVD9DXLUbwK8dgBeRj7DsKyrVtjMzMHG9tpeepzt_EktbRKG-rjezgGRb-421b8b3SNDBT9g_sTjV6VvxHdXS3zHAIU1nbxD66cEFVfoo7F63643RTEtjNJRB1mlicbJdknzoF-d-QFIh_xtTkTZjzoa7ZL7DrQQ
Token有三部分组成
第三部分 = 第一部分 + “.” + 第二部分
使用RSA验证共享token 方式一
1 static void Main(string[] args) 2 { 3 RSAParameters param = new RSAParameters() 4 { 5 Exponent = FromBase64Url("AQAB"), 6 Modulus = FromBase64Url("3JjRAiTwPOCDrWFcsHDovYDuBaqWuSSLxSp27Bc6rDnsepkw-yISVRlM4C-MwgL4wiDZnnQBv9-zZi8on0_L1L7UuuN5HFvvd01xApIWGdhJwQl2ijltwOKG8osxQQXtkDLOPOxhV-Fs5K9KQWW0AXdkPUXYulP3854MXmNWOqtC3fhMg78MUZAAKGrj67qk0tRdtxXIgF_Mqn5cMsEq8X7xWcbcPwWuc_NsfSxs43Y-p-7THMrv8vRzIQFUO27WGMZP9cTlXx0_OuhRaNnZUJoamp89-j-XMBfYzsCFyD_a3dBdUMvNtvgDq4IylA1oyT1F_J8xi4SPHwtNvnWxLQ"), 7 }; 8 9 RSA rSA = RSA.Create(param); 10 11 var ispass = rSA.VerifyData(UTF8Encoding.UTF8.GetBytes("eyJhbGciOiJSUzI1NiIsImtpZCI6IjAzZWNlODcyODlmZTU0ZWVlMzc3YTEzODBiMGU0OWEyIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1NDMxOTkxNTksImV4cCI6MTU0MzIwMjc1OSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9yZXNvdXJjZXMiLCJhcGkxIl0sImNsaWVudF9pZCI6Im12YyIsInN1YiI6IjQxMjciLCJhdXRoX3RpbWUiOjE1NDMxOTkxNTIsImlkcCI6ImxvY2FsIiwibmFtZSI6IjE4ODg4ODg4ODg3Iiwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSIsImFwaTEiLCJvZmZsaW5lX2FjY2VzcyJdLCJhbXIiOlsicHdkIl19"), 12 FromBase64Url("vGn_PiK_aomcVfGBGX0QH6-1ySd5Viv4ZHDA1s2xZ5HstgS07WawQpxbSG83aLAig_NpKiTR3aNZKTQ40s4PgUJLJ7x1WTaJUxJApWYi9oLsPxK6p7sxi3OPPHGWIKpka4ht_p_9SY13oToWvBECkhU6F2Oeo3iEKWmGy4n3aOLHuQe4rNEVrKnVD9DXLUbwK8dgBeRj7DsKyrVtjMzMHG9tpeepzt_EktbRKG-rjezgGRb-421b8b3SNDBT9g_sTjV6VvxHdXS3zHAIU1nbxD66cEFVfoo7F63643RTEtjNJRB1mlicbJdknzoF-d-QFIh_xtTkTZjzoa7ZL7DrQQ"), 13 HashAlgorithmName.SHA256, 14 RSASignaturePadding.Pkcs1); 15 } 16 17 public static byte[] FromBase64Url(string base64Url) 18 { 19 string padded = base64Url.Length % 4 == 0 20 ? base64Url : base64Url + "====".Substring(base64Url.Length % 4); 21 string base64 = padded.Replace("_", "/") 22 .Replace("-", "+"); 23 24 return Convert.FromBase64String(base64); 25 } 26
使用RSA验证共享token 方式二
1 string tokenStr = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAsCiAiY19oYXNoIjogIkxEa3RLZG9RYWszUGswY25YeENsdEEiCn0.XW6uhdrkBgcGx6zVIrCiROpWURs-4goO1sKA4m9jhJIImiGg5muPUcNegx6sSv43c5DSn37sxCRrDZZm4ZPBKKgtYASMcE20SDgvYJdJS0cyuFw7Ijp_7WnIjcrl6B5cmoM6ylCvsLMwkoQAxVublMwH10oAxjzD6NEFsu9nipkszWhsPePf_rM4eMpkmCbTzume-fzZIi5VjdWGGEmzTg32h3jiex-r5WTHbj-u5HL7u_KP3rmbdYNzlzd1xWRYTUs4E8nOTgzAUwvwXkIQhOh5TPcSMBYy6X3E7-_gr9Ue6n4ND7hTFhtjYs3cjNKIA08qm5cpVYFMFMG6PkhzLQ"; 2 string[] tokenParts = tokenStr.Split('.'); 3 4 RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); 5 rsa.ImportParameters( 6 new RSAParameters() { 7 Modulus = FromBase64Url("w7Zdfmece8iaB0kiTY8pCtiBtzbptJmP28nSWwtdjRu0f2GFpajvWE4VhfJAjEsOcwYzay7XGN0b-X84BfC8hmCTOj2b2eHT7NsZegFPKRUQzJ9wW8ipn_aDJWMGDuB1XyqT1E7DYqjUCEOD1b4FLpy_xPn6oV_TYOfQ9fZdbE5HGxJUzekuGcOKqOQ8M7wfYHhHHLxGpQVgL0apWuP2gDDOdTtpuld4D2LK1MZK99s9gaSjRHE8JDb1Z4IGhEcEyzkxswVdPndUWzfvWBBWXWxtSUvQGBRkuy1BHOa4sP6FKjWEeeF7gm7UMs2Nm2QUgNZw6xvEDGaLk4KASdIxRQ"), 8 Exponent = FromBase64Url("AQAB") 9 }); 10 11 SHA256 sha256 = SHA256.Create(); 12 byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(tokenParts[0] + '.' + tokenParts[1])); 13 14 RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa); 15 rsaDeformatter.SetHashAlgorithm("SHA256"); 16 if (rsaDeformatter.VerifySignature(hash, FromBase64Url(tokenParts[2]))) 17 MessageBox.Show("Signature is verified"); 18 19 //... 20 static byte[] FromBase64Url(string base64Url) 21 { 22 string padded = base64Url.Length % 4 == 0 23 ? base64Url : base64Url + "====".Substring(base64Url.Length % 4); 24 string base64 = padded.Replace("_", "/") 25 .Replace("-", "+"); 26 return Convert.FromBase64String(base64); 27 }
验证token是否过期
Token第二部分base64解码如下
{
“nbf”:1543199159,
“exp”:1543202759,
“iss”:“http://localhost:5000”,
“aud”:[“http://localhost:5000/resources”,“api1”],
“client_id”:“mvc”,
“sub”:“4127”,
“auth_time”:1543199152,
“idp”:“local”,
“name”:“18888888887”,
“scope”:[“openid”,“profile”,“api1”,“offline_access”],"
amr":[“pwd”]
}
其中exp保存过期时间,为unix时间戳,示例为2018/11/26 11:25:59过期
解密token中间部分时,报
Base-64 字符数组或字符串的长度无效等问题解决方案
string dummyData = Base64ToString(tokenParts[1]);
byte[] bytes = Convert.FromBase64String(dummyData);
string decode= Encoding.UTF8.GetString(bytes);
/// <summary> /// 对Base64字符串进行特殊字符替换,并进行PadRight操作,解决问题 /// </summary> /// <param name="base64"></param> /// <returns></returns> static string Base64ToString(string base64) { string dummyData = base64.Trim().Replace("%", "").Replace(",", "").Replace(" ", "+"); if (dummyData.Length % 4 > 0) { dummyData = dummyData.PadRight(dummyData.Length + 4 - dummyData.Length % 4, '='); } return dummyData; }
/// <summary> /// 将Java安全的base64字符串转换为byte数组 /// </summary> /// <param name="convert"></param> /// <param name="javaURLSafeString"></param> /// <returns></returns> public static byte[] FromBase64StringURLSafe(string javaURLSafeString) { javaURLSafeString = javaURLSafeString.Replace("-", "+").Replace("_", "/"); var base64 = Encoding.ASCII.GetBytes(javaURLSafeString); var padding = base64.Length * 3 % 4;//(base64.Length*6 % 8)/2 if (padding != 0) { javaURLSafeString = javaURLSafeString.PadRight(javaURLSafeString.Length + padding, '='); } return Convert.FromBase64String(javaURLSafeString); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
2021-06-10 react 受控组件
2021-06-10 react 生命周期之 更新阶段
2021-06-10 react 生命周期之 挂载阶段