什么是JWT
JWT:Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
传统的session认证
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.
基于session认证所显露的问题
Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
流程上是这样的:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)
策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *
。
那么我们现在回到JWT的主题上。
JWT的构成
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
C# MVC实现token
1.在NuGet中引用JWT
2.创建一个实体 UserInfo类
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace WebApplication1.model { public class UserInfo { public string UserName { get; set; } public string Pwd { get; set; } } }
3.创建JWT帮助类
using JWT; using JWT.Algorithms; using JWT.Serializers; using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace WebApplication1.model { public class JwtHelp { //私钥 web.config中配置 //"GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; private static string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; //ConfigurationManager.AppSettings["Secret"].ToString(); /// <summary> /// 生成JwtToken /// </summary> /// <param name="payload">不敏感的用户数据</param> /// <returns></returns> public static string SetJwtEncode(Dictionary<string, object> payload) { //格式如下 //var payload = new Dictionary<string, object> //{ // { "username","admin" }, // { "pwd", "claim2-value" } //}; IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJsonSerializer serializer = new JsonNetSerializer(); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); var token = encoder.Encode(payload, secret); return token; } /// <summary> /// 根据jwtToken 获取实体 /// </summary> /// <param name="token">jwtToken</param> /// <returns></returns> public static UserInfo GetJwtDecode(string token) { IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); var algorithm = new HMACSHA256Algorithm(); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm); var userInfo = decoder.DecodeToObject<UserInfo>(token, secret, verify: true);//token为之前生成的字符串 return userInfo; } } }
4.创建一个编码类DESCryption
using System; using System.Text; using System.Security.Cryptography; using System.IO; using System.Configuration; namespace JWT.MvcDemo.Help { public class DESCryption { /// <summary> /// //注意了,是8个字符,64位 /// </summary> private static string PrivateRsa = ConfigurationManager.AppSettings["PrivateRsa"]; /// <summary> /// //注意了,是8个字符,64位 /// </summary> private static string PublicRsa = ConfigurationManager.AppSettings["PublicRsa"]; /// <summary> /// 加密 /// </summary> /// <param name="data"></param> /// <returns></returns> public static string Encode(string data) { byte[] byKey = Encoding.ASCII.GetBytes(PrivateRsa); byte[] byIV = Encoding.ASCII.GetBytes(PublicRsa); DESCryptoServiceProvider cryptoProvider = new DESCryptoServiceProvider(); int i = cryptoProvider.KeySize; MemoryStream ms = new MemoryStream(); CryptoStream cst = new CryptoStream(ms, cryptoProvider.CreateEncryptor(byKey, byIV), CryptoStreamMode.Write); StreamWriter sw = new StreamWriter(cst); sw.Write(data); sw.Flush(); cst.FlushFinalBlock(); sw.Flush(); return Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length); } /// <summary> /// 解密 /// </summary> /// <param name="data"></param> /// <returns></returns> public static string Decode(string data) { byte[] byKey = Encoding.ASCII.GetBytes(PrivateRsa); byte[] byIV = Encoding.ASCII.GetBytes(PublicRsa); byte[] byEnc; try { byEnc = Convert.FromBase64String(data); } catch { return null; } DESCryptoServiceProvider cryptoProvider = new DESCryptoServiceProvider(); MemoryStream ms = new MemoryStream(byEnc); CryptoStream cst = new CryptoStream(ms, cryptoProvider.CreateDecryptor(byKey, byIV), CryptoStreamMode.Read); StreamReader sr = new StreamReader(cst); return sr.ReadToEnd(); } } }
5.创建一个返回消息类
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace JWT.MvcDemo.Models { public class DataResult { /// <summary> /// /// </summary> public string Token { get; set; } public bool Success { get; set; } public string Message { get; set; } } }
6.创建一个控制器用于生产token
using JWT.MvcDemo.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using WebApplication1.model; namespace WebApplication1.Controllers { public class JwtController : Controller { // GET: Jwt public ActionResult Index() { return View(); } /// <summary> /// 创建jwtToken /// </summary> /// <param name="username"></param> /// <param name="pwd"></param> /// <returns></returns> public ActionResult CreateToken(string username, string pwd) { DataResult result = new DataResult(); //假设用户名为"admin",密码为"123" if (username == "admin" && pwd == "123") { var payload = new Dictionary<string, object> { { "username",username }, { "pwd", pwd } }; result.Token = JwtHelp.SetJwtEncode(payload); result.Success = true; result.Message = "成功"; } else { result.Token = ""; result.Success = false; result.Message = "生成token失败"; } //return Json(result); //get请求需要修改成这样 return Json(result,JsonRequestBehavior.AllowGet); } } }
7.创建一个自定义过滤器
using JWT.MvcDemo.Help; using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Security; using WebApplication1.model; namespace JWT.MvcDemo.App_Start { public class MyAuthorizeAttribute : AuthorizeAttribute { private readonly string TimeStamp = ConfigurationManager.AppSettings["TimeStamp"]; /// <summary> /// 验证入口 /// </summary> /// <param name="filterContext"></param> public override void OnAuthorization(AuthorizationContext filterContext) { base.OnAuthorization(filterContext); } /// <summary> /// 验证核心代码 /// </summary> /// <param name="httpContext">fbc8ZBLd5ZbtCogcY9NUVV4HZbPln1lb</param> /// <returns></returns> protected override bool AuthorizeCore(HttpContextBase httpContext) { //前端请求api时会将token存放在名为"auth"的请求头中 var authHeader = httpContext.Request.Headers["auth"]; if (authHeader == null) return false; //请求参数 string requestTime = httpContext.Request["rtime"]; //请求时间经过DESC签名 if (string.IsNullOrEmpty(requestTime)) return false; //模拟生成rtime 时间戳,即登录的时间,加密.
//实际生产中这段代码应该在请求段。此处只为了程序验证通过 string r= DESCryption.Encode(DateTime.Now.ToString()); requestTime = r; //请求时间RSA解密后加上时间戳的时间即该请求的有效时间 DateTime Requestdt = DateTime.Parse(DESCryption.Decode(requestTime)).AddMinutes(int.Parse(TimeStamp)); DateTime Newdt = DateTime.Now; //服务器接收请求的当前时间 if (Requestdt < Newdt) { return false; } else { if (authHeader != null) { //进行其他操作 var userinfo = JwtHelp.GetJwtDecode(authHeader); //举个例子 生成jwtToken 存入redis中 //这个地方用jwtToken当作key 获取实体val 然后看看jwtToken根据redis是否一样 if (userinfo.UserName == "admin" && userinfo.Pwd == "123") return true; } } return false; } /// <summary> /// 验证失败处理 /// </summary> /// <param name="filterContext"></param> protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { base.HandleUnauthorizedRequest(filterContext); filterContext.Result = new RedirectResult("/Error"); filterContext.HttpContext.Response.Redirect("/Home/Error"); } } }
8.在要需要过滤的控制器方法上添加标签,标签就是自定义过滤器名称。
using JWT.MvcDemo.App_Start; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace WebApplication1.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } [HttpPost] [MyAuthorize] public string About() { string rtJson = "{\"code\": 0}"; try { rtJson = "{\"code\":0,\"data\":[],\"msg\":\"Your application description page.\",\"count\":1}"; } catch { rtJson = "{\"code\": 0}"; } return rtJson; } public ActionResult Contact() { ViewBag.Message = "Your contact page."; return View(); } } }
9.测试获取token
10.客户端将token放入header中达到携带token目的。
11.需要在web.config 中添加设置值
<add key="Secret" value="GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"/> <add key="PrivateRsa" value="GQDstcKs"/> <add key="PublicRsa" value="DVvVBrkx0"/> <add key="TimeStamp" value="2"/>