在先后端分离的项目中,首先咱们要解决的问题就是身份认证html
以往的时候,咱们使用cookie+session,或者只用cookie来保持会话。前端
一,先来复习一下cookie和sessionweb
首先咱们来复习一下在aspnet中cookie和session的关系,作一个简单试验redis
这是一个普通的view没有任何处理算法
能够看到,没有任何东西(cookie),而后当咱们写入一个session以后json
\后端
会发现多了一个名为ASP.NET_SessionId的cookie。咱们都知道在aspnet中,session是保存在服务器端的内存中的,而http协议是无状态的,那么他是怎么肯定不一样请求的sessionapi
没错,session是借助cookie来实现的:cookie中保存着 session的key,当咱们清除掉浏览器缓存时,会发现session也找不到了,就是这个缘由。跨域
使用session来保持会话有几个很严重的缺点:1 session容易丢失;2没法支持分布式;3,cookie 对跨域的支持很差浏览器
因此就用到了咱们今天说的token
二,token
1,token的产生
通常是用户登陆成功,服务器端产生一个token并返给前端,前端将token保存在cookie或者localStorage里面,而后每次请求时都带上这个token,通常都带在请求头里面
2,token的内容
通常的token里面必须有的是:1,会话用户的标识:好比userid。2,token的过时时间,若是想更完整一点,能够加上token的颁发者,签名等等
3,token的生成算法,通常是由服务器端将token的主要内容,过时时间等等作非对称加密,而后进行签名算法(防止客户端更改),具体看后面jwt
4,token校验
当服务器端收到请求时,首先会校验token,校验有两种不一样的方式
一, token产生后保存在服务器端(redis或者其余比较速度快的缓存中) 。优势:可控性强,能够用这个来作单点登陆,好比另外一个地方登陆,就remove掉以前的token。缺点:实现麻烦一点,并且要占服务器压力
二, token产生后服务器端不保存,只负责校验。 优势:大大下降了服务器的压力,实现起来,也要相对简单一点。缺点:token一旦颁发,服务器端就不可控了,只能等它过时。
具体用哪一种看具体的需求。若是不是作可控性要求很强,我的建议第二种。
5 jwt
jwt 全名Json Web Tokens,算是一种token的规范吧
园子里面有很不不错的介绍 ,好比这篇:阮一峰 jwt介绍 http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
组成有三部分
实现方式,将header部分和payload部分分别进行base64算法,而后用点号“.”隔开拼接,而后进行签名算法,而后在将三部分拼接(点号隔开)就获得了jwt
注意 ,jwt默认是采用base64编码的,也就是说 客户端也能解码得出具体内容的,因此除非特殊状况,重要敏感字段必定不能放在token中
如下是具体实现
using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Web; namespace Rk.JWT { public class Jwt { //参考自 阮一峰 jwt介绍 http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html public static string SALT = "OXpcRP8jmCfMKumY"; /// <summary> /// /// </summary> /// <param name="ExraPayload">额外的信息</param> /// <returns></returns> public static string Create(Dictionary<string,object> ExraPayload) { var Header = new Dictionary<string, string>(); Header.Add("tp", "MD5"); var Payload = new Dictionary<string, object>(); //JWT 规定了7个官方字段,供选用。 Payload.Add("iss", "signBy"); //颁发人 Payload.Add("jti", Guid.NewGuid().ToString()); //jwt的id Payload.Add("exp",System.DateTime.Now.AddMinutes(20));//过时时间 Payload.Add("nbf", System.DateTime.Now);//生效时间 Payload.Add("iat", System.DateTime.Now);//签发时间 Payload.Add("sub", "subject");//主题 Payload.Add("aud", "audience");//受众 foreach (var item in ExraPayload) { if (Payload.ContainsKey(item.Key)) { throw new Exception($"{item.Key}键值已被占用 不能使用 "); } else { Payload.Add(item.Key, item.Value); } } string base64Header = Base64Url(Newtonsoft.Json.JsonConvert.SerializeObject(Header)); string base64Payload = Base64Url(Newtonsoft.Json.JsonConvert.SerializeObject(Payload)); string tmp = base64Header + "." + base64Payload; string sign = Md5(tmp+ SALT);//加盐,重要 return base64Header+"."+ base64Payload+"."+ sign; } //校验是否合法,是否过时 public static bool Check(string token) { string base64Header = token.Split('.')[0]; string base64Payload = token.Split('.')[1]; string sign = token.Split('.')[2]; string tmp = base64Header + "." + base64Payload; var signCheck = Md5(base64Header + "." + base64Payload + SALT); if(signCheck!= sign) { return false; } var dic = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlDecode(base64Payload)); if( Convert.ToDateTime(dic["exp"])<System.DateTime.Now) { //过时了 return false; } return true; } //校验是否合法,是否过时 public static Dictionary<string,object> GetPayLoad(string token) { string base64Payload = token.Split('.')[1]; var dic = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlDecode(base64Payload)); return dic; } public static string Base64Url(string input) { //JWT 做为一个令牌(token),有些场合可能会放到 URL(好比 api.example.com/?token=xxx)。 //Base64 有三个字符+、/和=,在 URL 里面有特殊含义,因此要被替换掉:=被省略、+替换成-,/替换成_ 。 string output = ""; byte[] bytes = Encoding.UTF8.GetBytes(input); try { output = Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_').TrimEnd('=') ; } catch (Exception e) { throw e; } return output; } public static string Base64UrlDecode(string input) { string output = ""; input = input.Replace('-', '+').Replace('_', '/'); switch (input.Length % 4) { case 2: input += "=="; break; case 3: input += "="; break; } byte[] bytes = Convert.FromBase64String(input); try { output = Encoding.UTF8.GetString(bytes); } catch { output = input; } return output; } public static string Md5(string input,int bit=16) { MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider(); byte[] hashedDataBytes; hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(input)); StringBuilder tmp = new StringBuilder(); foreach (byte i in hashedDataBytes) { tmp.Append(i.ToString("x2")); } if (bit == 16) return tmp.ToString().Substring(8, 16); else if (bit == 32) return tmp.ToString();//默认状况 else return string.Empty; } } }
使用方式
public class HomeController : BaseController { public ActionResult Login(string username, string pwd) { /// 1, todo 验证用户名密码正确 //2,//在token中加入用户id,建立token var dic = new Dictionary<string, object>(); dic.Add("userid", "20125521225858"); string token = JWT.Jwt.Create(dic); //验证token是否正确是否过时 var isChecked = JWT.Jwt.Check(token); return Content(""); } }
下一篇咱们将会聊一聊 rest 风格url在先后端分离项目中的使用