Web开发:JWT 令牌原理简析和示例代码实现

一、格式

一个标准的 JWT 由三部分组成,用点来间隔,分别是:

  • 1.【无加密】Header(头部):描述令牌的类型和签名的算法。
  • 2.【无加密】Payload(负载):包含实际的数据(例如角色信息、用户信息等)。
  • 3.【通过密钥加密】Signature(签名):用来验证令牌的完整性和来源。

JWT 格式如下:

        header.payload.signature

二、原理

        当收到一个JWT令牌(由头部、负载和签名三部分组成)时,系统会根据令牌的头部和负载信息,使用预设的密钥和相应的签名算法重新计算签名。然后,将计算得到的签名与令牌中的签名部分进行比对。如果两者一致,则证明令牌未被篡改,鉴权成功。

三、优点

特性JWT普通Token
存储方式Token本身包含信息,无需存储在数据库Token需存储在数据库中,每次验证需查库
性能仅通过签名验证,快速,无需数据库查询每次请求都需查询数据库,可能造成延迟
扩展性适用于分布式系统,多个服务器共享需要集中存储,跨多个服务器时较难扩展
安全性使用签名验证,难以伪造,且可设定有效期数据库泄露可能导致安全问题
灵活性可嵌入自定义数据,如角色、权限等只能存储Token本身,需额外请求数据
可用性有效期内可独立验证,适合无状态应用需要依赖数据库验证,适合集中式应用

四、代码演示

1.安装Nuget包

System.IdentityModel.Tokens.Jwt

2.生成和解析JWTtoken

class Program
{
    public static string Key = "MyJwtDemo";
    public static byte[] ByteKey = GetByteKey(Key);
    static void Main(string[] args)
    {
        // 1. 创建JWT Token
        string token = GenerateJwtToken("小明", "123456", "管理员");
        Console.WriteLine("生成的JWT Token: ");
        Console.WriteLine(token);

        // 2. 解析JWT Token
        ParseJwtToken(token);
    }

    //获取密钥(长度为256的字节数组形式)
    static byte[] GetByteKey(string key)
    {
        using (SHA256 sha256 = SHA256.Create())
        {
            byte[] keyBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(key));
            return keyBytes;
        }
    }

    // 生成JWT Token
    static string GenerateJwtToken(string username, string userid, string role)
    {
        // 密钥,通常在生产环境中应存储在安全地方
        var secretKey = new SymmetricSecurityKey(ByteKey);
        var signingCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);

        var claims = new[]
        {
            new Claim(ClaimTypes.Name, username), // 用户名
            new Claim(ClaimTypes.NameIdentifier, userid), // 用户id
            new Claim(ClaimTypes.Role, role) // 角色
        };

        var jwtSecurityToken = new JwtSecurityToken(
            issuer: "yourIssuer", // 可选,发行者
            audience: "yourAudience", // 可选,受众
            claims: claims,
            expires: DateTime.Now.AddHours(1), // 设置过期时间
            signingCredentials: signingCredentials);

        var tokenHandler = new JwtSecurityTokenHandler();
        string token = tokenHandler.WriteToken(jwtSecurityToken);

        return token;
    }

    // 解析JWT Token
    static void ParseJwtToken(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        try
        {
            // 验证并解析Token
            var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = true,
                IssuerSigningKey = new SymmetricSecurityKey(ByteKey)
            }, out var validatedToken);

            // 提取并打印Claims信息
            var jwtToken = (JwtSecurityToken)validatedToken;
            Console.WriteLine("Token解析结果: ");
            Console.WriteLine($"用户名: {principal.Identity.Name}");
            Console.WriteLine($"用户账号: {principal.FindFirst(ClaimTypes.NameIdentifier)?.Value}");
            Console.WriteLine($"角色: {principal.FindFirst(ClaimTypes.Role)?.Value}");
        }
        catch (Exception ex)
        {
            Console.WriteLine("无效的Token: " + ex.Message);
        }
    }
}

3.解析头部和负载

namespace JWTTokenDemo
{
    class Program
    {
        static void Main()
        {
            // JWT Token
            string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoi5bCP5piOIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiIxMjM0NTYiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiLnrqHnkIblkZgiLCJleHAiOjE3NDYwMDg1NjcsImlzcyI6InlvdXJJc3N1ZXIiLCJhdWQiOiJ5b3VyQXVkaWVuY2UifQ.ZddlswAbFlFE5RrfT4OAnzsSzM0Gx8i3qe8zd467LmE";

            // 解析 JWT Token
            var parts = token.Split('.');

            // 解码 Header 部分
            string header = Base64UrlDecode(parts[0]);
            Console.WriteLine("Header: " + header);

            // 解码 Payload 部分
            string payload = Base64UrlDecode(parts[1]);
            Console.WriteLine("Payload: " + payload);
        }

        // Base64Url 解码函数
        public static string Base64UrlDecode(string input)
        {
            string output = input;
            output = output.Replace('-', '+');  // 将 Base64Url 的 '-' 替换为 '+'
            output = output.Replace('_', '/');  // 将 Base64Url 的 '_' 替换为 '/'

            switch (output.Length % 4)  // Base64的填充
            {
                case 2: output += "=="; break;
                case 3: output += "="; break;
            }

            byte[] bytes = Convert.FromBase64String(output);
            return Encoding.UTF8.GetString(bytes);
        }
    }
}

输出结果:

Header: {"alg":"HS256","typ":"JWT"}
Payload: {"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"小明","http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier":"123456","http://schemas.microsoft.com/ws/2008/06/identity/claims/role":"管理员","exp":1746008567,"iss":"yourIssuer","aud":"yourAudience"}

注意:上面的1746008567是时间戳,代表的日期是2025-04-30 18:22:47 

4.token的校验

namespace JWTTokenDemo
{
    class Program
    {
        static void Main()
        {
            // JWT Token
            string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoi5bCP5piOIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiIxMjM0NTYiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiLnrqHnkIblkZgiLCJleHAiOjE3NDYwMDg1NjcsImlzcyI6InlvdXJJc3N1ZXIiLCJhdWQiOiJ5b3VyQXVkaWVuY2UifQ.ZddlswAbFlFE5RrfT4OAnzsSzM0Gx8i3qe8zd467LmE";
            string Key = "MyJwtDemo";// 替换为你的密钥
            byte[] ByteKey = GetByteKey(Key);

            // 解析 JWT Token
            var parts = token.Split('.');
            string signature = parts[2]; // 这是 JWT 中的签名部分

            // 重新计算签名
            string data = parts[0] + "." + parts[1];  // 使用头部和负载来计算签名
            string calculatedSignature = ComputeHmacSha256Signature(data, ByteKey);

            // 验证签名
            if (calculatedSignature == signature)
            {
                Console.WriteLine("签名验证成功");
            }
            else
            {
                Console.WriteLine("签名验证失败.");
            }

        }

        //获取密钥(长度为256的字节数组形式)
        public static byte[] GetByteKey(string key)
        {
            using (SHA256 sha256 = SHA256.Create())
            {
                byte[] keyBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(key));
                return keyBytes;
            }
        }

        // Base64Url 解码函数
        public static string Base64UrlDecode(string input)
        {
            string output = input;
            output = output.Replace('-', '+');  // 将 Base64Url 的 '-' 替换为 '+'
            output = output.Replace('_', '/');  // 将 Base64Url 的 '_' 替换为 '/'

            switch (output.Length % 4)  // Base64的填充
            {
                case 2: output += "=="; break;
                case 3: output += "="; break;
            }

            byte[] bytes = Convert.FromBase64String(output);
            return Encoding.UTF8.GetString(bytes);
        }

        // 使用 HMACSHA256 算法计算签名
        public static string ComputeHmacSha256Signature(string data, byte[] secretKey)
        {
            using (HMACSHA256 hmac = new HMACSHA256(secretKey))
            {
                byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
                return Base64UrlEncode(hashBytes); // 返回 Base64Url 编码的签名
            }
        }

        // Base64Url 编码函数
        public static string Base64UrlEncode(byte[] input)
        {
            string base64 = Convert.ToBase64String(input);
            base64 = base64.Split('=')[0];  // 去掉 Base64 后缀的 '='
            base64 = base64.Replace('+', '-');  // 将 Base64 的 '+' 替换为 '-'
            base64 = base64.Replace('/', '_');  // 将 Base64 的 '/' 替换为 '_'
            return base64;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值