在前后端分离的项目中,首先我们要解决的问题就是身份认证
以往的时候,我们使用cookie+session,或者只用cookie来保持会话。
一,先来复习一下cookie和session
首先我们来复习一下在aspnet中cookie和session的关系,做一个简单试验
这是一个普通的view没有任何处理
可以看到,没有任何东西(cookie),然后当我们写入一个session之后
会发现多了一个名为ASP.NET_SessionId的cookie。我们都知道在aspnet中,session是保存在服务器端的内存中的,而http协议是无状态的,那么他是怎么确定不同请求的session
没错,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(头部,一般包含了token的签名方式)
- Payload(负载,也就是具体的有效部分)
- Signature(签名,将前两部分进行签名算法,防止客户端篡改)
实现方式,将header部分和payload部分分别进行base64算法,然后用点号“.”隔开拼接,然后进行签名算法,然后在将三部分拼接(点号隔开)就得到了jwt
注意 ,jwt默认是采用base64编码的,也就是说 客户端也能解码得出具体内容的,所以除非特殊情况,重要敏感字段一定不能放在token中
以下是具体实现
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Security.Cryptography; 5 using System.Text; 6 using System.Web; 7 8 namespace Rk.JWT 9 { 10 11 public class Jwt 12 { 13 14 //参考自 阮一峰 jwt介绍 http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html 15 16 17 18 public static string SALT = "OXpcRP8jmCfMKumY"; 19 20 21 22 /// <summary> 23 /// 24 /// </summary> 25 /// <param name="ExraPayload">额外的信息</param> 26 /// <returns></returns> 27 public static string Create(Dictionary<string,object> ExraPayload) 28 { 29 var Header = new Dictionary<string, string>(); 30 Header.Add("tp", "MD5"); 31 var Payload = new Dictionary<string, object>(); 32 33 //JWT 规定了7个官方字段,供选用。 34 Payload.Add("iss", "signBy"); //颁发人 35 Payload.Add("jti", Guid.NewGuid().ToString()); //jwt的id 36 Payload.Add("exp",System.DateTime.Now.AddMinutes(20));//过期时间 37 Payload.Add("nbf", System.DateTime.Now);//生效时间 38 Payload.Add("iat", System.DateTime.Now);//签发时间 39 Payload.Add("sub", "subject");//主题 40 Payload.Add("aud", "audience");//受众 41 42 foreach (var item in ExraPayload) 43 { 44 if (Payload.ContainsKey(item.Key)) 45 { 46 47 throw new Exception($"{item.Key}键值已被占用 不能使用 "); 48 } 49 else 50 { 51 Payload.Add(item.Key, item.Value); 52 } 53 } 54 string base64Header = Base64Url(Newtonsoft.Json.JsonConvert.SerializeObject(Header)); 55 string base64Payload = Base64Url(Newtonsoft.Json.JsonConvert.SerializeObject(Payload)); 56 string tmp = base64Header + "." + base64Payload; 57 58 string sign = Md5(tmp+ SALT);//加盐,重要 59 60 return base64Header+"."+ base64Payload+"."+ sign; 61 } 62 63 64 //校验是否合法,是否过期 65 public static bool Check(string token) 66 { 67 string base64Header = token.Split(‘.‘)[0]; 68 string base64Payload = token.Split(‘.‘)[1]; 69 string sign = token.Split(‘.‘)[2]; 70 string tmp = base64Header + "." + base64Payload; 71 var signCheck = Md5(base64Header + "." + base64Payload + SALT); 72 if(signCheck!= sign) 73 { 74 return false; 75 } 76 var dic = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlDecode(base64Payload)); 77 if( Convert.ToDateTime(dic["exp"])<System.DateTime.Now) 78 { 79 //过期了 80 return false; 81 82 } 83 return true; 84 85 } 86 //校验是否合法,是否过期 87 public static Dictionary<string,object> GetPayLoad(string token) 88 { 89 90 string base64Payload = token.Split(‘.‘)[1]; 91 92 var dic = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlDecode(base64Payload)); 93 94 return dic; 95 96 } 97 98 99 public static string Base64Url(string input) 100 { 101 //JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。 102 //Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。 103 string output = ""; 104 byte[] bytes = Encoding.UTF8.GetBytes(input); 105 try 106 { 107 output = Convert.ToBase64String(bytes).Replace(‘+‘, ‘-‘).Replace(‘/‘, ‘_‘).TrimEnd(‘=‘) ; 108 109 110 } 111 catch (Exception e) 112 { 113 throw e; 114 } 115 return output; 116 } 117 public static string Base64UrlDecode(string input) 118 { 119 string output = ""; 120 121 input = input.Replace(‘-‘, ‘+‘).Replace(‘_‘, ‘/‘); 122 switch (input.Length % 4) 123 { 124 case 2: 125 input += "=="; 126 break; 127 case 3: 128 input += "="; 129 break; 130 } 131 byte[] bytes = Convert.FromBase64String(input); 132 try 133 { 134 output = Encoding.UTF8.GetString(bytes); 135 } 136 catch 137 { 138 output = input; 139 } 140 return output; 141 } 142 public static string Md5(string input,int bit=16) 143 { 144 145 MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider(); 146 byte[] hashedDataBytes; 147 hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(input)); 148 StringBuilder tmp = new StringBuilder(); 149 foreach (byte i in hashedDataBytes) 150 { 151 tmp.Append(i.ToString("x2")); 152 } 153 if (bit == 16) 154 return tmp.ToString().Substring(8, 16); 155 else 156 if (bit == 32) return tmp.ToString();//默认情况 157 else return string.Empty; 158 159 } 160 } 161 162 }
使用方式
1 public class HomeController : BaseController 2 { 3 4 5 public ActionResult Login(string username, string pwd) 6 { 7 8 /// 1, todo 验证用户名密码正确 9 10 11 //2,//在token中加入用户id,创建token 12 var dic = new Dictionary<string, object>(); 13 dic.Add("userid", "20125521225858"); 14 string token = JWT.Jwt.Create(dic); 15 16 17 //验证token是否正确是否过期 18 var isChecked = JWT.Jwt.Check(token); 19 20 return Content(""); 21 22 } 23 }
下一篇我们将会聊一聊 rest 风格url在前后端分离项目中的使用
原文地址:https://www.cnblogs.com/wahson2019/p/10860973.html