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] 标记公开接口,实现权限控制。

相关推荐
智驱力人工智能2 分钟前
小区高空抛物AI实时预警方案 筑牢社区头顶安全的实践 高空抛物检测 高空抛物监控安装教程 高空抛物误报率优化方案 高空抛物监控案例分享
人工智能·深度学习·opencv·算法·安全·yolo·边缘计算
懒人咖8 分钟前
缺料分析时携带用料清单的二开字段
c#·金蝶云星空
数据与后端架构提升之路30 分钟前
论系统安全架构设计及其应用(基于AI大模型项目)
人工智能·安全·系统安全
bugcome_com1 小时前
深入了解 C# 编程环境及其开发工具
c#
市场部需要一个软件开发岗位2 小时前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
lingggggaaaa2 小时前
安全工具篇&动态绕过&DumpLsass凭据&Certutil下载&变异替换&打乱源头特征
学习·安全·web安全·免杀对抗
凯子坚持 c2 小时前
CANN-LLM:基于昇腾 CANN 的高性能、全功能 LLM 推理引擎
人工智能·安全
wfserial3 小时前
c#使用微软自带speech选择男声仍然是女声的一种原因
microsoft·c#·speech
QT.qtqtqtqtqt3 小时前
未授权访问漏洞
网络·安全·web安全
阔皮大师4 小时前
INote轻量文本编辑器
java·javascript·python·c#