来源: Aspnet Mvc 前后端分离项目手记(二)关于token认证 – 小小爵 – 博客园
在前后端分离的项目中,首先我们要解决的问题就是身份认证
以往的时候,我们使用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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
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; } } } |
使用方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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在前后端分离项目中的使用