网站开发作业总结,线上购买链接,刚刚地震最新消息今天 刚才,个人信息怎么在百度推广一. 简介
1. 背景 传统的基于Session的校验存在诸多问题#xff0c;比如#xff1a;Session过期、服务器开销过大、不能分布式部署、不适合前后端分离的项目。 传统的基于Token的校验需要存储Key-Value信息#xff0c;存在Session或数据库中都有弊端#xff0c;如果按照一…一. 简介
1. 背景 传统的基于Session的校验存在诸多问题比如Session过期、服务器开销过大、不能分布式部署、不适合前后端分离的项目。 传统的基于Token的校验需要存储Key-Value信息存在Session或数据库中都有弊端如果按照一定规律采用对称加密算法生成token虽然能解决上面问题但是一旦对称加密算法泄露很容被反编译所以在此基础上继续升级利用userId生成Token只要保存好秘钥即可从而引出JWT。
2. 什么是JWT Json web token 简称JWT 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。该token被设计为紧凑且安全的特别适用于分布式站点的单点登录SSO场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息以便于从资源服务器获取资源也可以增加一些额外的其它业务逻辑所必须的声明信息该token也可直接被用于认证也可被加密。
下面就是一段JWT字符串(后面详细分析)
1 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiJ9.Qjw1epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U
3. JWT的优点
(1). JWT是无状态的不需要服务器端保存会话信息减轻服务器端的读取压力存储在客户端上同时易于扩展、易于分布式部署。
(2). JWT可以跨语言支持。
(3). 便于传输jwt的构成很简单字节占用空间少所以是非常便于传输的。
(4). 自身构成有payload部分可以存储一下业务逻辑相关的非敏感信息。 特别声明JWT最大的优势是无状态的相对传统的Session验证能减轻服务器端的存储压力安全性更高但也不是绝对的比如针对同一个接口JWT字符串被截取后且在有效期内在不篡改JWT字符串的情况下也是可以模拟请求进行访问的。随着下面的内容深入体会JWT的核心
二. JWT深度剖析
1. JWT的长相 下面的一段字符串就是JWT加密后的显示格式我们仔细看中间通过两个 “点” 将这段字符串分割成三部分了。
eyJ0eXAxIjoiMTIzNCIsImFsZzIiOiJhZG1pbiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiIsImV4cCI6MTU1MjI4Njc0Ni44Nzc0MDE4fQ.pEgdmFAy73walFonEm2zbxg46Oth3dlT02HR9iVzXa8 上面一段很长的字符串到底是怎么来的呢就需要了解JWT的构成原理。
2. JWT的构成 JWT由三部分组成如下图分别是Header头部、Payload负载、Signature签名。 (1). 头部Header 通常包括两部分类型如 “typ”“JWT”和加密算法如“alg”:HS256,当然你也可以添加其它自定义的一些参数然后对这个对象机型base64编码生成一段字符串
如“eyJ0eXAxIjoiMTIzNCIsImFsZzIiOiJhZG1pbiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0”我们可以对其进行反编码一下看一下其庐山真面目。 注Base64是一种编码也就是说它是可以被翻译回原来的样子来的。它并不是一种加密过程。
(2). 负载Payload 通常用来存放一些业务需要但不敏感的信息比如用户编号(userId)、用户账号(userAccount)、权限等等该部分也有一些默认的声明如下图很多不常用。
iss: jwt签发者sub: jwt所面向的用户aud: 接收jwt的一方exp: jwt的过期时间这个过期时间必须要大于签发时间nbf: 定义在什么时间之前该jwt都是不可用的.iat: jwt的签发时间jti: jwt的唯一身份标识主要用来作为一次性token,从而回避重放攻击。
其中最常用的就是exp过期时间要和1970年1月1日那个点进行比对用法如下下面表示生成jwt字符串后20分钟过期。 最后对该部分组装成的对象进行base64编码如“eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiIsImV4cCI6MTU1MjI4Njc0Ni44Nzc0MDE4fQ”我们可以对其进行反编码看一下庐山真面目如下图 注该部分也是可以解码的所以不要存储敏感信息。
(3). 签名(Signature) 这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串然后通过header中声明的加密方式进行加盐secret组合加密然后就构成了jwt的第三部分。
伪代码如下
1 var encodedString base64UrlEncode(header) . base64UrlEncode(payload);
2 var signature HMACSHA256(encodedString, sercret密钥) 说明密钥存在服务器端不要泄露在不知道密钥的情况下是不能进行解密的jwt的签发生成也是在服务器端的secret就是用来进行jwt的签发和jwt的验证所以它就是你服务端的私钥在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。 特别说明即使payload中的信息被篡改服务器端通过signature就可以判断出来是非法请求即校验不能通过。
3. 代码尝鲜 需要通过Nuget装JWT包新版本的jwt建议.Net 版本4.6起。 1 [HttpGet]2 public string JiaM()3 {4 //设置过期时间(可以不设置下面表示签名后 20分钟过期)5 double exp (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;6 var payload new Dictionarystring, object7 {8 { UserId, 123 },9 { UserName, admin },
10 {exp,exp } //该参数也可以不写
11 };
12 var secret GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk;//不要泄露
13 IJwtAlgorithm algorithm new HMACSHA256Algorithm();
14
15 //注意这个是额外的参数默认参数是 typ 和alg
16 var headers new Dictionarystring, object
17 {
18 { typ1, 1234 },
19 { alg2, admin }
20 };
21
22 IJsonSerializer serializer new JsonNetSerializer();
23 IBase64UrlEncoder urlEncoder new JwtBase64UrlEncoder();
24 IJwtEncoder encoder new JwtEncoder(algorithm, serializer, urlEncoder);
25 var token encoder.Encode(headers, payload, secret);
26 return token;
27 }
28
29 [HttpGet]
30 public string JieM(string token)
31 {
32 var secret GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk;
33 try
34 {
35 IJsonSerializer serializer new JsonNetSerializer();
36 IDateTimeProvider provider new UtcDateTimeProvider();
37 IJwtValidator validator new JwtValidator(serializer, provider);
38 IBase64UrlEncoder urlEncoder new JwtBase64UrlEncoder();
39 IJwtDecoder decoder new JwtDecoder(serializer, validator, urlEncoder);
40 var json decoder.Decode(token, secret, true);
41 return json;
42 }
43 catch (TokenExpiredException)
44 {
45 //过期了自动进入这里
46 return Token has expired;
47 }
48 catch (SignatureVerificationException)
49 {
50 //校验未通过自动进入这里
51 return Token has invalid signature;
52 }
53 catch (Exception)
54 {
55 //其它错误自动进入到这里
56 return other error;
57 } 上述代码方便通过PostMan进行快速测试注意解密的方法中的三个catchtoken过期会自动进入到TokenExpiredException异常中token验证不通过会自动进入SignatureVerificationException中。 三. JWT的使用流程 整体流程大致如下图 1. 客户端(前端或App端)通过一个Http请求把用户名和密码传到登录接口建议采用Https的模式避免信息被嗅探。
2. 服务器端校验登录的接口验证用户名和密码通过后把一些业务逻辑需要的信息如userId、userAccount放到Payload中进而生成一个xxx.yyy.zzz形式的JWT字符串返回给客户端。
3. 客户端获取到JWT的字符串可以存放到LocalStorage中注意退出的登录的时候删除该值。
4. 登录成功每次请求其它接口的时候都在表头带着该jwt字符串建议放入HTTP Header中的Authorization位。(解决XSS和XSRF问题) 或者自己命名比如“auth”进行该字符串的传递。
5. 服务器端要写一个过滤器在该过滤器中进行校验jwt的有效性(签名是否正确、是否过期)验证通过进行接口的业务逻辑验证不通过返回给客户端。
这里要解决两个问题
(1). 在WebApi的过滤器中如果校验通过了如何将解密后的值传递到action中。解密两次就有点坑了
(2). 在WebApi的过滤器中如果校验不通过如何返回给客户端然后客户端针对这种情况又该如何接受呢。
实战中揭晓。 四. 项目实战
一. 整体目标 通过一个登陆接口和一个获取信息的接口模拟JWT的整套验证逻辑。
二. 详细步骤
1. 封装JWT加密和解密的方法。 需要通过Nuget安装JWT的程序集JWT的最新版本建议使用.Net 4.6 起。 JWTHelp
2. 模拟登陆接口 在登录接口中模拟数据库校验即账号和密码为admin和12345即校验通过然后把账号和userId实际应该到数据库中查这里也可以设置一下过期时间比如20分钟一同存放到PayLoad中然后生成JWT字符串返回给客户端。 /// summary/// 模拟登陆/// /summary/// param nameuserAccount/param/// param namepwd/param/// returns/returns[HttpGet]public string Login1(string userAccount, string pwd){try{//这里模拟数据操作只要是admin和123456就验证通过if (userAccount admin pwd 123456){//1. 进行业务处理这里模拟获取userIdstring userId 0806;//过期时间(可以不设置下面表示签名后 20分钟过期)double exp (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;//进行组装var payload new Dictionarystring, object{{userId, userId },{userAccount, userAccount },{exp,exp }};//2. 进行JWT签名var token JWTHelp.JWTJiaM(payload);var result new { result ok, token token };return JsonConvert.SerializeObject(result);}else{var result new { result error, token };return JsonConvert.SerializeObject(result);}}catch (Exception){var result new { result error, token };return JsonConvert.SerializeObject(result);}} 3. 客户端调用登录接口 这里只是单纯为了测试使用的get请求实际项目中建议post请求且配置Https请求成功后把jwt字符串存放到localStorage中。 1 //1.登录2 $(#j_jwtLogin).on(click, function () {3 $.get(/api/Seventh/Login1, { userAccount: admin, pwd: 123456 }, function (data) {4 var jsonData JSON.parse(data);5 if (jsonData.result ok) {6 console.log(jsonData.token);7 //存放到本地缓存中8 window.localStorage.setItem(token, jsonData.token);9 alert(登录成功,ticket jsonData.token);
10 } else {
11 alert(登录失败);
12 }
13 });
14 }); 运行结果 4. 服务器端过滤器 代码中分享了两种获取header中信息的方式获取到“auth”后进行校验校验不通过的话通过状态码401返回给客户端校验通过的话则使用 actionContext.RequestContext.RouteData.Values.Add(auth, result); 进行解密值的存储方便后续action的直接获取。 1 /// summary2 /// 验证JWT算法的过滤器3 /// /summary4 public class JWTCheck : AuthorizeAttribute5 {6 public override void OnAuthorization(HttpActionContext actionContext)7 {8 //获取表头Header中值的几种方式9 //方式一
10 //{
11 // var authHeader2 from t in actionContext.Request.Headers
12 // where t.Key auth
13 // select t.Value.FirstOrDefault();
14 // var token2 authHeader2.FirstOrDefault();
15 //}
16
17 //方式二
18 IEnumerablestring auths;
19 if (!actionContext.Request.Headers.TryGetValues(auth, out auths))
20 {
21 //HttpContext.Current.Response.Write(报文头中的auth为空);
22 //返回状态码验证未通过并返回原因(前端进行401状态码的捕获)注意这句话并不能阶段该过滤器还会继续往下走要借助if-else
23 actionContext.Response actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError(报文头中的auth为空));
24 }
25 else
26 {
27 var token auths.FirstOrDefault();
28 if (token ! null)
29 {
30 if (!string.IsNullOrEmpty(token))
31 {
32 var result JWTHelp.JWTJieM(token);
33 if (result expired)
34 {
35 //返回状态码验证未通过并返回原因(前端进行401状态码的捕获)
36 actionContext.Response actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError(expired));
37 }
38 else if (result invalid)
39 {
40 //返回状态码验证未通过并返回原因(前端进行401状态码的捕获)
41 actionContext.Response actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError(invalid));
42 }
43 else if (result error)
44 {
45 //返回状态码验证未通过并返回原因(前端进行401状态码的捕获)
46 actionContext.Response actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError(error));
47 }
48 else
49 {
50 //表示校验通过,用于向控制器中传值
51 actionContext.RequestContext.RouteData.Values.Add(auth, result);
52 }
53 }
54 }
55 else
56 {
57 //返回状态码验证未通过并返回原因(前端进行401状态码的捕获)
58 actionContext.Response actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError(token 空));
59 }
60 }
61
62 }
63 } 5.服务器端获取信息的的方法 将上说过滤器以特性的形式作用在该方法中然后通过 RequestContext.RouteData.Values[auth] 获取到解密后的值进而进行其它业务处理。 1 /// summary2 /// 加密后的获取信息3 /// /summary4 /// returns/returns5 [JWTCheck]6 [HttpGet]7 public string GetInfor()8 {9 var userData JsonConvert.DeserializeObjectuserData(RequestContext.RouteData.Values[auth].ToString()); ;
10 if (userData null)
11 {
12 var result new { Message error, data };
13 return JsonConvert.SerializeObject(result);
14 }
15 else
16 {
17 var data new { userId userData.userId, userAccount userData.userAccount };
18 var result new { Message ok, data data };
19 return JsonConvert.SerializeObject(result);
20 }
21 } 6. 客户端调用获取信息的方法 前端获取到localStorage中token值采用自定义header的方式以“auth”进行传递调用服务器端的方法由于服务器的验证token不正确的时候是以状态码的形式返回所以这里要采用error方法通过xhr.status401进行判断凡是进入到这个401中均是token验证没有通过具体是什么原因可以通过xhr.responseText获取详细的值进行判断。 1 //2.获取信息2 $(#j_jwtGetInfor).on(click, function () {3 //从本地缓存中读取token值4 var token window.localStorage.getItem(token);5 $.ajax({6 url: /api/Seventh/GetInfor,7 type: Get,8 data: {},9 datatype: json,
10 //设置header的方式1
11 headers: { auth: token},
12 //设置header的方式2
13 //beforeSend: function (xhr) {
14 // xhr.setRequestHeader(auth, token)
15 //},
16 success: function (data) {
17 console.log(data);
18 var jsonData JSON.parse(data);
19 if (jsonData.Message ok) {
20 var myData jsonData.data;
21 console.log(获取成功);
22 console.log(myData.userId);
23 console.log(myData.userAccount);
24 } else {
25 console.log(获取失败);
26 }
27 },
28 //当安全校验未通过的时候进入这里
29 error: function (xhr) {
30 if (xhr.status 401) {
31 console.log(xhr.responseText);
32 var jsonData JSON.parse(xhr.responseText);
33 console.log(授权失败,原因为 jsonData.Message);
34 }
35 }
36 });
37 }); 运行结果 其他的如token过期只需要改一下电脑时间即可以测试token不正确改一下获取到的jwt字符串可以测试这里不再进行 了。