C#中运用JWT机制进行安全传输和信息认证

一、概念

JWT是目前最流行的跨域认证解决方案,是一种基于Token的认证授权机制,用于在网络上安全传输和验证信息,通常被用于登录验证。

JWT 由三部分组成(详情参考:https://www.jwt.io):
头部(Header) ------Json对象,通常用于声明类型和声明所使用的算法,如:{ "alg": "HS256", "typ": "JWT"}。
载荷(Payload) ------Json对象,包含了具体的用户信息,例如用户ID、用户名、角色、有效时间等,如:{"name":"user","role":"admin","exp":63082282600,"iat":63082281600}。这部分默认是不加密的,一定不要将隐私信息存放其中。
签名(Signature)------它使用Header和Payload中的数据以及一个密钥使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成签名。签名的目的是保证消息没有被篡改。

这三部分使用(.)进行分隔,形成一个字符串,生成的字符串通常显示为:xxxxxx.yyyyyy.zzzzzz,即:base64编码的Header.base64编码的Payload.签名字符串,也就是<base64UrlEncoded(Header)>.<base64UrlEncoded(Payload)>.<signature>

用户登录时,服务器根据登录信息,通过 Payload、Header 和 Secret创建Token并将 Token 发送给客户端。客户端接收到 Token 之后,会将其保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌,服务器端根据令牌进行身份验证,并从中获取用户相关信息。

二、服务器端代码

1、JwtHelper.cs,JWT主文件,包含生成JWTToken方法、验证签名是否有效的方法等

cs 复制代码
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Web.Script.Serialization;

namespace JwtTest.Helpers{
    public class JwtHelper{
        //HMACSHA256加密的密钥
        private static readonly string secretKey = "YourSecretKey";

        /// <summary>生成JWT Token</summary>
        public static string GenerateToken(string userName,string role,int expireMinutes = 120) {
            var header = new { alg = "HS256",typ = "JWT" };         //构建Header
            var payload = new {                                     //构建Payload
                name = userName,                                    //用户名
                role = role,                                        //用户权限
                //签发时间(UTC时间2000年1月1日以来的秒数,long型整数)
                iat=DateTime.Now.ToUniversalTime().Ticks/10000000-63082281600
                //过期时间(签发时间+expireMinutes参数指定的分钟数)
                exp = DateTime.Now.AddMinutes(expireMinutes).ToUniversalTime().Ticks/10000000-63082281600,
            };
            var serializer = new JavaScriptSerializer();
            string headerJson = serializer.Serialize(header);
            string payloadJson=serializer.Serialize(payload);
            //通过自定义的符合JWT标准的Base64Url编码要求的编码解码方法Base64UrlEncode进行编码
            string headerBase64 = Base64UrlEncode(Encoding.UTF8.GetBytes(headerJson));
            string payloadBase64=Base64UrlEncode(Encoding.UTF8.GetBytes(payloadJson));

            string signature = GenereatSignature($"{headerBase64}.{payloadBase64}",secretKey);  //生成签名

            return $"{headerBase64}.{payloadBase64}.{signature}";
        }

        /// <summary>验证Token是否有效</summary>
        public static bool ValidateToken(string token) {
            try {
                string[] parts = token.Split('.');
                if(parts.Length!=3) return false;
                //验证签名
                string headerPayload = $"{parts[0]}.{parts[1]}";
                string signature = GenereatSignature(headerPayload,secretKey);
                if(signature!=parts[2]) return false;
                //验证过期时间
                string payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(parts[1]));
                var payload = new JavaScriptSerializer().Deserialize<Dictionary<string,object>>(payloadJson);

                if(!payload.ContainsKey("exp")) return false;
                double expTime = Convert.ToDouble(payload["exp"]);
                double currentTime = DateTime.Now.ToUniversalTime().Ticks/10000000-62135596800;

                return currentTime<expTime;
            } catch { return false; }

        }

        /// <summary>从Token中解析用户信息</summary>
        public static Dictionary<string,object> GetUserInfoFromToken(string token) {
            try {
                string[] parts = token.Split('.');
                string payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(parts[1]));
                return new JavaScriptSerializer().Deserialize<Dictionary<string,object>>(payloadJson);
            }catch { return null; }
        }

        /// <summary>用符合JWT标准的要求进行编码解码</summary>
        /*
        符合JWT标准的Base64Url编码要求如下:
        将标准Base64编码中的+替换为-,将/替换为_。 ‌
        去掉编码结果末尾的=填充符。 ‌
        对JWT的Header和Payload部分分别进行Base64Url编码。 ‌
‌        编码目的‌:确保编码后的字符串可以在URL、HTTP头等环境中安全传输,避免特殊字符引起解析问题。 ‌
        */
        private static string Base64UrlEncode(byte[] bytes) {
            string base64=Convert.ToBase64String(bytes);
            return base64.Replace('+','-').Replace('/','_').TrimEnd('=');
        }
        private static byte[] Base64UrlDecode(string input) {
            input=input.Replace('-','+').Replace('_','/');
            //base64编码用4位长度表示一个3字节的字符(UTF8编码中,汉字为3字节,字母为1字节),当字符长度不足3字节时,在末尾用=号进行补足
            while(input.Length%4!=0)
                input+="=";
            return Convert.FromBase64String(input);
        }
        
        /// <summary>生成HMACSHA256签名</summary>
        private static string GenereatSignature(string data,string key) {
            using(var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key))) {
                byte[] hash=hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
                return Base64UrlEncode(hash);
            }
        }

    }
}

2、DefaultController.cs,MVC项目中控制器代码,包含登录验证Login方法(返回JWT Token字符器)和带Token访问的GetUserInfo方法

cs 复制代码
using JwtTest.Helpers;
using System.Web.Mvc;

namespace JwtTest.Controllers{
    public class DefaultController : Controller{

        ///客户端登录成功后,可将返回的JWT Token写入Cookie或localStorage或SessionStorage,以便后续将此作为参数放在Header请求头或使用QueryString参数访问其他页面///
        public ActionResult Login(string userName,string role,string password) {
            if(userName=="username"&&password=="password") {
                string token=JwtHelper.GenerateToken(userName,role,120);
                return Json(new { token },JsonRequestBehavior.AllowGet);
            }
            return new HttpStatusCodeResult(401,"登录失败");
        }

        public ActionResult GetUserInfo() {
            string token = null;
            var authheader = Request.Headers["Authorization"];
            if(!string.IsNullOrEmpty(authheader)&&authheader.StartsWith("Bearer "))
                token=authheader.Substring(7);
            else
                token=Request.QueryString["token"];

            var dic=JwtHelper.GetUserInfoFromToken(token);
            return Json(dic,JsonRequestBehavior.AllowGet);
        }
    }
}

在实际使用中,客户端一般是将token存放在请求头的Authorization,并加上Bearer标注(格式:headers: { Authorization: Bearer <token> })

建议创建认证过滤器Filter,拦截需要权限的接口请求并验证Token,控制器通过 [JwtAuthFilter] 特性标记需要认证的接口,[AllowAnonymous] 标记公开接口,实现权限控制。

相关推荐
yngsqq18 小时前
平面图环 内轮廓
c#
BenSmith19 小时前
从零上手嵌入式 RTOS:以 Raspberry Pi Pico 2 WH 为例的烧录、定制构建与多系统对比指南
安全
汇智信科20 小时前
训练安全管理系统:赋能军消装备训练,实现全流程智能化管控
安全·训练安全管理·装备训练智能化·军事训练保障·消防训练管理
rockey62720 小时前
AScript之eval函数详解
c#·.net·script·eval·expression·动态脚本
其实防守也摸鱼1 天前
CTF密码学综合教学指南--第三章
开发语言·网络·python·安全·网络安全·密码学
其实防守也摸鱼1 天前
CTF密码学综合教学指南--第四章
网络·笔记·安全·网络安全·密码学·ctf
DevilSeagull1 天前
电脑上安装的服务会自动消失? 推荐项目: localhostSCmanager. 更好管理你的服务!
测试工具·安全·react·vite·localhost·hono·trpc
He少年1 天前
【AI 辅助案例分享】
人工智能·c#·编辑器·ai编程
@insist1231 天前
信息安全-防火墙技术演进全景:从代理NAT 到下一代及专项防火墙
网络·安全·web安全·软考·信息安全工程师·软件水平考试
工程师0071 天前
栈和堆的概念
c#·栈和堆