首页 > 其他 > 详细

手写JWT加解密

时间:2021-04-18 14:34:14      阅读:30      评论:0      收藏:0      [点我收藏+]

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 匿名对象中添加指定项

 

手写JWT加解密

原文:https://www.cnblogs.com/huawublog/p/14673105.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!