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