2023年6月20日发(作者:)
token暴露安全吗_第⼋节:常见安全隐患和传统的基于Session和Token的安全校验⼀. 常见的安全隐患1. SQL注⼊常见的案例:String query = "SELECT * FROM T_User WHERE userID='" + Request["userID"] + "';这个时候,只需要在传递过来的userID后⾯加上个: or 1=1,即可以获取T_User表中的所有数据了。解决⽅案:参数化查询。2. 跨站脚本攻击(Cross-Site Scripting (XSS))允许跨站脚本是Web 2.0时代⽹站最普遍的问题。如果⽹站没有对⽤户提交的数据加以验证⽽直接输出⾄⽹页,那么恶意⽤户就可以在⽹页中注⼊脚本来窃取⽤户数据。eg:通过后台代码编写前端代码进⾏输出1 string page += "";攻击者只要输⼊以下数据:'>'当该数据被输出到页⾯的时候,每个访问该页⾯的⽤户cookie就⾃动被提交到了攻击者定义好的⽹站。解决⽅案:输⼊的数据要进⾏安全校验和转义3.跨站请求伪造(Cross-Site Request Forgery (CSRF) )同样是跨站请求,这种与上⾯XSS的不同之处在于这个请求是从钓鱼⽹站上发起的。⽐如钓鱼⽹站包含了下⾯代码:这⾏代码的作⽤就是⼀个在⽹站的转帐请求,客户访问钓鱼⽹站时,如果也同时登录了或者保留了的登录状态,那个相应的隐藏请求就会被成功执⾏。解决⽅案:使⽤Token校验,保存好Token,⽐如:JWT校验。⼆. 两类系统要解决的常见问题1. Web系统(1).是否登录. 没有登录话是不能进⼊登录以外的页⾯,即使访问,也要返回到⾃动进⼊登录页⾯。(2).是否有权限. 权限的展现分两种:a. 没有权限的话直接不显⽰. b. 没有权限但是显⽰,单击的时候提⽰没有权限。接⼝(1).接⼝安全,不是任何⼈都能访问的,必须登录后才能访问,当然也有⼀部分不需要登录。(2).防⽌接⼝被知道参数后任何能直接访问,要有校验,即使地址暴露别⼈也访问不通。三. 传统的基于Session的校验1. 前⾔基于Session的校验,通常是⽤在管理系统中或者⽹站上,不适⽤于APP接⼝或者前后端分离的项⽬。2. 步骤①:登录成功,将⽤户信息(⼀个实体)和该⽤户对应的权限信息存放到Session中。②:对所有的页⾯的展⽰的地址(前提需要登录后才能显⽰的),加上⼀个过滤器,在过滤器中判断该⽤户是否登录过,没有登录的话直接退回到登录页⾯。③:对所有的业务操作的⽅法加上⼀个过滤器,在过滤器中判断该⽤户是否该权限,没有的话,直接提⽰没有权限。注:以上②和③中的过滤器⾥,都需要到Session中取值。3. 基于Session验证的弊端①:Session经常过期回收,导致Session为空,是⼀些业务操作莫名其妙的没法使⽤。可以改进为使⽤数据库的Session,会好很多。②:由于Session的原理可知,在同⼀个浏览器中,先后⽤不同账号登录,先登录的账号Session中的信息会被后登陆账号Session中的信息覆盖。③:每个⽤户登录⼀次,就需要往Session做⼀次记录,⽽Session默认是保存在服务器内存中的,随着认证的⽤户增多,服务器端开销明显增⼤。④:不能进⾏负载均衡,保存在内存中,下次还需要到这台服务器上才能拿到授权。⑤:Session是基于Cookie,如果Cookie被截获,⽤户很容易受到跨站请求伪造攻击(CSRF)。四. 传统的基于Token的校验1. 背景APP项⽬或者其它前后端分离的项⽬,Session验证⽤不了,只能⽤基于token的验证。当然Web项⽬也可以采⽤这种⽅式。2. 步骤①:通过账号和密码登录成功,服务端⽣成⼀个token(⽐如:32位不重复的随机字符串)。②:服务端把该token和⽤户id保存到数据库(SQLServer或Redis)或者Session中,然后把token值返回给前端。③:客户端每次请求都带上该token,服务端根据该token来查询是否合法和过期,然后去数据库中查出来⽤户id进⾏使⽤。3. 弊端①:验证信息如果存在数据库中,每次都要根据token查⽤户id,增加了数据库的开销。②:验证信息如果存在Session中,则增⼤了服务器端存储的压⼒。③:token⼀旦被截取,就很容易进⾏跨站请求伪造。4. 鉴于以上弊端进⾏思考①:如果token遵从⼀定规律,使⽤对称加密算法来加密⽤户id⽣成token,服务器端只要解密该token,就能知道⽤户id了,不需要额外的开销。但是,如果对称加密算法泄露了,别⼈也可以伪造token了。②:如果我们⽤⾮对称加密算法来做呢,保存好秘钥,就不存在上⾯的问题了,从⽽引出JWT类似于该原理。5. 实战案例(基于Token的⼩升级)A. 步骤(1) 登录成功,将账号和密码按照⼀定格式进⾏拼接成字符串,然后进⾏票据加密(对称加密,这其中可以设置很多东西,⽐如过期时间),将⽣成的ticket返回给客户端。(2) 客户端可以把该ticket值存到localstorage中,每次请求在表头进⾏附加。(3) 服务器端写⼀个过滤器,对该ticket进⾏解密,拿到账号和密码,去数据库中查询,是否匹配,如果匹配则验证通过。B. 深度分析同样存在被截取的问题,加密算法如果被⼈知道,容易被伪造服务器端验证:见. 代码分享(1). 服务器端校验登录的代码1 ///2 ///模拟登陆3 ///4 ///5 ///6 ///7 [HttpGet]8 public string Login0(string userAccount, stringpwd)9 {10 //这⾥实际应该去数据库验证,此处暂时⽤固定值写死11 if (userAccount == "admin" && pwd == "123456")12 {13 FormsAuthenticationTicket ticketObject = newFormsAuthenticationTicket(0, userAccount, , rs(1), true, $"{userAccount}&{pwd}",ookiePath);14 var result = new { result = "ok", ticket=t(ticketObject) };15 izeObject(result);16 }17 else18 {19 var result = new { result = "error"};20 izeObject(result);21 }22 }(2). 客户端调⽤登录的代码获取成功,将token值存放到localStorage中。1 $.get("/api/Seventh/Login0", { userAccount: "admin", pwd: "123456"}, function (data) {2 var jsonData=(data);3 if ( == "ok") {4 ();5 //存放到本地缓存中6 m("myTicket", );7 alert("登录成功,ticket=" +);8 } else{9 alert("登录失败");10 }11 });(3). 服务器端过滤器代码该过滤器中通过ization 获取固定的参数位:Authorization,然后通过ter 获取参数值,前端需要分割⼀下,如下:当然还有很多别的赋值和获取的⽅式,详细的见下⾯章节。1 ///2 ///基于token的⼩升级3 ///过滤器4 ///5 public classCheckPer0 : AuthorizeAttribute6 {7 public override voidOnAuthorization(HttpActionContext actionContext)8{9 //1. 获取报⽂头(固定的参数位 Authorization)10 var authorizationModel =ization;11 //2. 如果标注了[AllowAnonymous]特性,则不进⾏验证12 if (tomAttributes(true).Count != 013 || tomAttributes(true).Count != 0)14 {15//orization(actionContext);16 }17 else if (authorizationModel != null && ter != null)18 {19 try20 {21 //逻辑验证22 //解密23 var strTicket =t(ter).UserData;24 //此处应该去数据库验证25 if (("admin&123456"))26 {27 //表⽰校验通过,⽤于向控制器中传值28 ("auth", strTicket);29 }30 else31 {32 UnauthorizedRequest(actionContext);//返回没有授权33 }34 }35 catch(Exception)36 {3738 UnauthorizedRequest(actionContext);//返回没有授权39 }40 }41 }42 }(4). 服务器端加密后获取信息的代码1 ///2 ///加密后获取信息3 ///4 ///5 [HttpGet]6 [CheckPer0]7 public stringGetInfor0()8 {9 var userData =["auth"].ToString();10 if (OrEmpty(userData))11 {12 var result = new {Message = "error", data = ""};13 izeObject(result);14 }15 else16 {17 var result = new { Message = "ok", data =userData };18 izeObject(result);19 }20 }(5). 客户端调⽤获取信息的代码1 //从本地缓存中读取token值2 var myTicket = m("myTicket");3 $.ajax({4 url: "/api/Seventh/GetInfor0",5 type: "Get",6 data: {},7datatype: "json",8 beforeSend: function (xhr) {9 //Authorization 是⼀个固定的参数位置,本来就有这个参数,后台⽅便获取,当然也可以⾃⼰命名10 //在myTicket前⾯加个BasicAuth1 只是为了后台.Parameter能获取到⽽已,⾄于叫什么名,没有关系11 uestHeader("Authorization", 'BasicAuth1' +myTicket)12 },13 success: function (data) {14 (data);15alert(data);16 },17 //当安全校验未通过的时候进⼊这⾥18 error: function (xhr) {19 if ( == 401) {20 (seText);21 alert("授权未通过");22 }23 }24 });(6). 运⾏结果!作 者 : Yaopengfei(姚鹏飞)声 明1 : 本⼈才疏学浅,⽤郭德纲的话说“我是⼀个⼩学⽣”,如有错误,欢迎讨论,请勿谩骂^_^。声 明2 : 原创博客请在转载时保留原⽂链接或在⽂章开头加上本⼈博客地址,否则保留追究法律责任的权利。
2023年6月20日发(作者:)
token暴露安全吗_第⼋节:常见安全隐患和传统的基于Session和Token的安全校验⼀. 常见的安全隐患1. SQL注⼊常见的案例:String query = "SELECT * FROM T_User WHERE userID='" + Request["userID"] + "';这个时候,只需要在传递过来的userID后⾯加上个: or 1=1,即可以获取T_User表中的所有数据了。解决⽅案:参数化查询。2. 跨站脚本攻击(Cross-Site Scripting (XSS))允许跨站脚本是Web 2.0时代⽹站最普遍的问题。如果⽹站没有对⽤户提交的数据加以验证⽽直接输出⾄⽹页,那么恶意⽤户就可以在⽹页中注⼊脚本来窃取⽤户数据。eg:通过后台代码编写前端代码进⾏输出1 string page += "";攻击者只要输⼊以下数据:'>'当该数据被输出到页⾯的时候,每个访问该页⾯的⽤户cookie就⾃动被提交到了攻击者定义好的⽹站。解决⽅案:输⼊的数据要进⾏安全校验和转义3.跨站请求伪造(Cross-Site Request Forgery (CSRF) )同样是跨站请求,这种与上⾯XSS的不同之处在于这个请求是从钓鱼⽹站上发起的。⽐如钓鱼⽹站包含了下⾯代码:这⾏代码的作⽤就是⼀个在⽹站的转帐请求,客户访问钓鱼⽹站时,如果也同时登录了或者保留了的登录状态,那个相应的隐藏请求就会被成功执⾏。解决⽅案:使⽤Token校验,保存好Token,⽐如:JWT校验。⼆. 两类系统要解决的常见问题1. Web系统(1).是否登录. 没有登录话是不能进⼊登录以外的页⾯,即使访问,也要返回到⾃动进⼊登录页⾯。(2).是否有权限. 权限的展现分两种:a. 没有权限的话直接不显⽰. b. 没有权限但是显⽰,单击的时候提⽰没有权限。接⼝(1).接⼝安全,不是任何⼈都能访问的,必须登录后才能访问,当然也有⼀部分不需要登录。(2).防⽌接⼝被知道参数后任何能直接访问,要有校验,即使地址暴露别⼈也访问不通。三. 传统的基于Session的校验1. 前⾔基于Session的校验,通常是⽤在管理系统中或者⽹站上,不适⽤于APP接⼝或者前后端分离的项⽬。2. 步骤①:登录成功,将⽤户信息(⼀个实体)和该⽤户对应的权限信息存放到Session中。②:对所有的页⾯的展⽰的地址(前提需要登录后才能显⽰的),加上⼀个过滤器,在过滤器中判断该⽤户是否登录过,没有登录的话直接退回到登录页⾯。③:对所有的业务操作的⽅法加上⼀个过滤器,在过滤器中判断该⽤户是否该权限,没有的话,直接提⽰没有权限。注:以上②和③中的过滤器⾥,都需要到Session中取值。3. 基于Session验证的弊端①:Session经常过期回收,导致Session为空,是⼀些业务操作莫名其妙的没法使⽤。可以改进为使⽤数据库的Session,会好很多。②:由于Session的原理可知,在同⼀个浏览器中,先后⽤不同账号登录,先登录的账号Session中的信息会被后登陆账号Session中的信息覆盖。③:每个⽤户登录⼀次,就需要往Session做⼀次记录,⽽Session默认是保存在服务器内存中的,随着认证的⽤户增多,服务器端开销明显增⼤。④:不能进⾏负载均衡,保存在内存中,下次还需要到这台服务器上才能拿到授权。⑤:Session是基于Cookie,如果Cookie被截获,⽤户很容易受到跨站请求伪造攻击(CSRF)。四. 传统的基于Token的校验1. 背景APP项⽬或者其它前后端分离的项⽬,Session验证⽤不了,只能⽤基于token的验证。当然Web项⽬也可以采⽤这种⽅式。2. 步骤①:通过账号和密码登录成功,服务端⽣成⼀个token(⽐如:32位不重复的随机字符串)。②:服务端把该token和⽤户id保存到数据库(SQLServer或Redis)或者Session中,然后把token值返回给前端。③:客户端每次请求都带上该token,服务端根据该token来查询是否合法和过期,然后去数据库中查出来⽤户id进⾏使⽤。3. 弊端①:验证信息如果存在数据库中,每次都要根据token查⽤户id,增加了数据库的开销。②:验证信息如果存在Session中,则增⼤了服务器端存储的压⼒。③:token⼀旦被截取,就很容易进⾏跨站请求伪造。4. 鉴于以上弊端进⾏思考①:如果token遵从⼀定规律,使⽤对称加密算法来加密⽤户id⽣成token,服务器端只要解密该token,就能知道⽤户id了,不需要额外的开销。但是,如果对称加密算法泄露了,别⼈也可以伪造token了。②:如果我们⽤⾮对称加密算法来做呢,保存好秘钥,就不存在上⾯的问题了,从⽽引出JWT类似于该原理。5. 实战案例(基于Token的⼩升级)A. 步骤(1) 登录成功,将账号和密码按照⼀定格式进⾏拼接成字符串,然后进⾏票据加密(对称加密,这其中可以设置很多东西,⽐如过期时间),将⽣成的ticket返回给客户端。(2) 客户端可以把该ticket值存到localstorage中,每次请求在表头进⾏附加。(3) 服务器端写⼀个过滤器,对该ticket进⾏解密,拿到账号和密码,去数据库中查询,是否匹配,如果匹配则验证通过。B. 深度分析同样存在被截取的问题,加密算法如果被⼈知道,容易被伪造服务器端验证:见. 代码分享(1). 服务器端校验登录的代码1 ///2 ///模拟登陆3 ///4 ///5 ///6 ///7 [HttpGet]8 public string Login0(string userAccount, stringpwd)9 {10 //这⾥实际应该去数据库验证,此处暂时⽤固定值写死11 if (userAccount == "admin" && pwd == "123456")12 {13 FormsAuthenticationTicket ticketObject = newFormsAuthenticationTicket(0, userAccount, , rs(1), true, $"{userAccount}&{pwd}",ookiePath);14 var result = new { result = "ok", ticket=t(ticketObject) };15 izeObject(result);16 }17 else18 {19 var result = new { result = "error"};20 izeObject(result);21 }22 }(2). 客户端调⽤登录的代码获取成功,将token值存放到localStorage中。1 $.get("/api/Seventh/Login0", { userAccount: "admin", pwd: "123456"}, function (data) {2 var jsonData=(data);3 if ( == "ok") {4 ();5 //存放到本地缓存中6 m("myTicket", );7 alert("登录成功,ticket=" +);8 } else{9 alert("登录失败");10 }11 });(3). 服务器端过滤器代码该过滤器中通过ization 获取固定的参数位:Authorization,然后通过ter 获取参数值,前端需要分割⼀下,如下:当然还有很多别的赋值和获取的⽅式,详细的见下⾯章节。1 ///2 ///基于token的⼩升级3 ///过滤器4 ///5 public classCheckPer0 : AuthorizeAttribute6 {7 public override voidOnAuthorization(HttpActionContext actionContext)8{9 //1. 获取报⽂头(固定的参数位 Authorization)10 var authorizationModel =ization;11 //2. 如果标注了[AllowAnonymous]特性,则不进⾏验证12 if (tomAttributes(true).Count != 013 || tomAttributes(true).Count != 0)14 {15//orization(actionContext);16 }17 else if (authorizationModel != null && ter != null)18 {19 try20 {21 //逻辑验证22 //解密23 var strTicket =t(ter).UserData;24 //此处应该去数据库验证25 if (("admin&123456"))26 {27 //表⽰校验通过,⽤于向控制器中传值28 ("auth", strTicket);29 }30 else31 {32 UnauthorizedRequest(actionContext);//返回没有授权33 }34 }35 catch(Exception)36 {3738 UnauthorizedRequest(actionContext);//返回没有授权39 }40 }41 }42 }(4). 服务器端加密后获取信息的代码1 ///2 ///加密后获取信息3 ///4 ///5 [HttpGet]6 [CheckPer0]7 public stringGetInfor0()8 {9 var userData =["auth"].ToString();10 if (OrEmpty(userData))11 {12 var result = new {Message = "error", data = ""};13 izeObject(result);14 }15 else16 {17 var result = new { Message = "ok", data =userData };18 izeObject(result);19 }20 }(5). 客户端调⽤获取信息的代码1 //从本地缓存中读取token值2 var myTicket = m("myTicket");3 $.ajax({4 url: "/api/Seventh/GetInfor0",5 type: "Get",6 data: {},7datatype: "json",8 beforeSend: function (xhr) {9 //Authorization 是⼀个固定的参数位置,本来就有这个参数,后台⽅便获取,当然也可以⾃⼰命名10 //在myTicket前⾯加个BasicAuth1 只是为了后台.Parameter能获取到⽽已,⾄于叫什么名,没有关系11 uestHeader("Authorization", 'BasicAuth1' +myTicket)12 },13 success: function (data) {14 (data);15alert(data);16 },17 //当安全校验未通过的时候进⼊这⾥18 error: function (xhr) {19 if ( == 401) {20 (seText);21 alert("授权未通过");22 }23 }24 });(6). 运⾏结果!作 者 : Yaopengfei(姚鹏飞)声 明1 : 本⼈才疏学浅,⽤郭德纲的话说“我是⼀个⼩学⽣”,如有错误,欢迎讨论,请勿谩骂^_^。声 明2 : 原创博客请在转载时保留原⽂链接或在⽂章开头加上本⼈博客地址,否则保留追究法律责任的权利。
发布评论