手写JWT加解密
C#生成的JWT Token,java不认,
看了下java JWT的生成源码,自己动手实现了一个C#版的JWT生成工具
目的是为了调试线上java的jwt接口
线上项目请使用成熟的jwt组件
参考了网上大佬的文章,简单封装一个开箱即用的工具类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace System
{
/// <summary>
/// 实现JWT加密和校验
/// </summary>
public class MyJWTTools
{
/// <summary>
/// 生成JWT
/// </summary>
/// <param name="sub">jwt所面向的用户</param>
/// <param name="exp">jwt的过期时间,这个过期时间必须要大于签发时间</param>
/// <param name="secretKey">密钥</param>
/// <param name="SerializePayloadToJsonFunc">参数为payload的类型,返回序列化后的json字符串</param>
/// <param name="defEncoding">信息部分的编码</param>
/// <returns></returns>
public static string CreateJWT(String sub, DateTime exp, byte[] secretKey,
Func<dynamic, String> SerializePayloadToJsonFunc,
Encoding defEncoding = null)
{
defEncoding = defEncoding ?? Encoding.UTF8;
// 表头的处理
String headerBase64Str;
{
var headerJson = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
headerBase64Str = Base64Encode(defEncoding.GetBytes(headerJson));
}
// PayLoad的处理
String payloadBase64Str;
{
var jwtPayLoad = new
{
// jwt所面向的用户
sub = sub,
// jwt的签发时间
iat = DateTime.Now.ToUTC(),// 1618571381,
// jwt的过期时间,这个过期时间必须要大于签发时间
exp = exp.ToUTC(),//1618671381,
};
var payLoadJson = SerializePayloadToJsonFunc(jwtPayLoad);//JsonTextHelper.SerializeObject(jwtPayLoad);
payloadBase64Str = Base64Encode(defEncoding.GetBytes(payLoadJson));
}
// Sign的处理
String signBase64;
{
var sign = $"{headerBase64Str}.{payloadBase64Str}".ToHMACSHA256(secretKey, defEncoding);
signBase64 = Base64Encode(sign);
}
// 最终的jwt字符串
string jwtStr = $"{headerBase64Str}.{payloadBase64Str}.{signBase64}";
return jwtStr;
}
/// <summary>
///
/// </summary>
/// <typeparam name="T">JWTPayLoad的类型</typeparam>
/// <param name="jwtStr"></param>
/// <param name="secretKey">密钥</param>
/// <param name="DeserializePayloadFunc">参数为payloadJson字符串,返回payload对象</param>
/// <param name="payloadModel">解析的payload数据</param>
/// <param name="defEncoding">payload的编码类型,用于从jwt字符串中解码出原始payload</param>
/// <returns></returns>
public static bool Check<T>(String jwtStr, byte[] secretKey,
Func<String, T> DeserializePayloadFunc,
out T payloadModel,
Encoding defEncoding = null)
where T : IJWTPayload
{
// 校验token是否正确
bool result; //True表示通过,False表示未通过
var jwtArr = jwtStr.Split('.');
//2.1. 获取token中的PayLoad中的值,并做过期校验
var payload = jwtArr[1];
var payLoadBase64 = Base64Decode(payload);
var payloadStr = defEncoding.GetString(payLoadBase64);
payloadModel = DeserializePayloadFunc(payloadStr);//JsonConvert.DeserializeObject<T>(payloadStr); //这一步已经获取到了payload中的值,并进行转换了
var nowTime = DateTime.Now.ToUTC();
if (nowTime > payloadModel.exp)
{
//表示token过期,校验未通过
result = false;
return result;
}
else
{
//2.2 做准确性校验
var oldSignBase64Str = jwtArr[2];
var newSign = $"{jwtArr[0]}.{jwtArr[1]}".ToHMACSHA256(secretKey, defEncoding);
var newSignBase64Str = Base64Encode(newSign);
return oldSignBase64Str.EqualIgnoreCase(newSignBase64Str); //true表示检验通过,false表示检验未通过
}
}
// Base64编码
private static string Base64Encode(byte[] data)
{
var base64 = Convert.ToBase64String(data).Replace('+', '-').Replace('/', '_').TrimEnd('=');
return base64;
}
// Base64解码
private static byte[] Base64Decode(string base64UrlStr)
{
base64UrlStr = base64UrlStr.Replace('-', '+').Replace('_', '/');
switch (base64UrlStr.Length % 4)
{
case 2:
base64UrlStr += "==";
break;
case 3:
base64UrlStr += "=";
break;
}
var bytes = Convert.FromBase64String(base64UrlStr);
return bytes;
}
private byte[] Base64UrlDecode3(string base64Str)
{
//base64UrlStr = Uri.EscapeDataString(base64UrlStr);
base64Str = base64Str.Replace('-', '+').Replace('_', '/');
switch (base64Str.Length % 4)
{
case 2:
base64Str += "==";
break;
case 3:
base64Str += "=";
break;
}
var bytes = Convert.FromBase64String(base64Str);
return bytes;
}
/// <summary>
/// 转换为单字节 java base64
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private static string JavaBase64(string str)
{
byte[] by = Encoding.UTF8.GetBytes(str);
sbyte[] sby = new sbyte[by.Length];
for (int i = 0; i < by.Length; i++)
{
if (by[i] > 127)
sby[i] = (sbyte)(by[i] - 256);
else
sby[i] = (sbyte)by[i];
}
byte[] newby = (byte[])(object)sby;
return Convert.ToBase64String(newby);
}
}
/// <summary>
///
/// </summary>
public interface IJWTPayload
{
/// <summary>jwt所面向的用户</summary>
String sub { get; set; }
/// <summary>
/// jwt的签发时间,
/// UTC 时间,毫秒
/// </summary>
long iat { get; set; }
/// <summary>
/// jwt的过期时间,这个过期时间必须要大于签发时间,
/// UTC 时间,毫秒
/// </summary>
long exp { get; set; }
}
}
一些扩展方法
/// <summary>
///
/// </summary>
/// <param name="val">待加密的值</param>
/// <param name="secret">密钥</param>
/// <param name="defEncoding">待加密的值的编码类型</param>
/// <returns></returns>
public static byte[] ToHMACSHA256(this string val, byte[] secret, Encoding defEncoding = null)
{
defEncoding = defEncoding ?? Encoding.UTF8;
var messageBytes = defEncoding.GetBytes(val);
return messageBytes.ToHMACSHA256(secret);
}
public class JWTPayloadModel : IJWTPayload
{
public string sub { get; set; }
public long iat { get; set; }
public long exp { get; set; }
}
测试Demo
// jwt的key 一定是Base64编码过的
var keyBase64Str = "QyM2NjY=";// 实际文本为:C#666
Console.WriteLine($"创建JWT");
var payLoad = "C#、JAVA JWT标准还不一样,坑尼玛的爹啊";
var signKeyArr = Convert.FromBase64String(keyBase64Str);
var expTime = DateTime.Now.AddYears(1);
var token = MyJWTTools.CreateJWT(
payLoad, expTime,
signKeyArr,
d => JsonTextHelper.SerializeObject(d),
Encoding.UTF8);
Console.WriteLine(token);
Console.WriteLine($"校验JWT");
var result = MyJWTTools.Check<JWTPayloadModel>(token, signKeyArr,
d => JsonTextHelper.DeserializeObject<JWTPayloadModel>(d),
out JWTPayloadModel jWTPayloadModel,
Encoding.UTF8);
Console.WriteLine(result);
Console.WriteLine(JsonTextHelper.SerializeObject(jWTPayloadModel));

建议线上项目使用成熟的jwt组件
试了一下,C#的jwt组件需要校验多个payload项,导致校验payload失败,
可以在生成的方法里 jwtPayLoad 匿名对象中添加指定项

浙公网安备 33010602011771号