Cookie
Cookie最主要的作用就是用于會(huì)話管理,當(dāng)用戶登錄一個(gè)網(wǎng)站時(shí),服務(wù)器會(huì)生成一個(gè)包含會(huì)話ID的Cookie并發(fā)送給瀏覽器,瀏覽器將這個(gè)Cookie保存在本地。此后,每次用戶發(fā)送請(qǐng)求時(shí),瀏覽器都會(huì)自動(dòng)將這個(gè)Cookie發(fā)送給服務(wù)器,服務(wù)器通過會(huì)話ID識(shí)別用戶身份,從而保持用戶的登錄狀態(tài)。Cookie除了用于會(huì)話管理,還可以在指客戶端持久存儲(chǔ),一般使用kv形式保存。
cookie屬性
可以看一下瀏覽器持久化存儲(chǔ)的cookie內(nèi)容:
Name/value | 鍵值對(duì),cookie的內(nèi)容。 |
Domain | Domain決定Cookie在哪個(gè)域是有效的,也就是決定在向該域發(fā)送請(qǐng)求時(shí)是否攜帶此Cookie,Domain的設(shè)置是對(duì)子域生效的,如Doamin設(shè)置為 .a.com,則b.a.com和c.a.com均可使用該Cookie,但如果設(shè)置為b.a.com,則c.a.com不可使用該Cookie。Domain參數(shù)必須以點(diǎn)(“.”)開始。 |
Path | Cookie的有效路徑,和Domain類似,也對(duì)子路徑生效。 |
Expires/Max-Age | 用于指定Cookie的過期時(shí)間。 |
?HttpOnly | 此屬性指定Cookie只能通過HTTP協(xié)議訪問。 |
Secure | Cookie的安全屬性,若設(shè)置為true,則瀏覽器只會(huì)在HTTPS和SSL等安全協(xié)議中傳輸此Cookie,不會(huì)在HTTP協(xié)議中傳輸此Cookie。 |
SameSite | 該屬性用于限制第三方Cookie,防止跨站請(qǐng)求偽造(CSRF)攻擊。
瀏覽器一般會(huì)默認(rèn)限制cookie不支持跨域,如果跨域需要配置合理的CORS策略,即配置白名單。 |
Session
當(dāng)客戶端第一次訪問服務(wù)器的時(shí)候,此時(shí)客戶端的請(qǐng)求中不攜帶任何標(biāo)識(shí)給服務(wù)器,此時(shí)服務(wù)器會(huì)新建session標(biāo)識(shí),當(dāng)服務(wù)器進(jìn)行響應(yīng)的時(shí)候,服務(wù)器會(huì)將session標(biāo)識(shí)放到http響應(yīng)頭的Set-Cookie中,以key-value的形式返回給客戶端,例:
SESSIONID=xxx;
客戶在下次訪問服務(wù)端的時(shí)候,將sessionid帶上,服務(wù)端根據(jù)sessionid從本地內(nèi)存獲取客戶會(huì)話信息。
由于會(huì)有越來越多的用戶訪問服務(wù)器,因此服務(wù)端的Session也會(huì)越來越多。為防止內(nèi)存溢出,服務(wù)器會(huì)把長時(shí)間(默認(rèn)30分鐘)沒有活躍的Session從內(nèi)存刪除。如果超過了超時(shí)時(shí)間沒訪問過服務(wù)器,Session就自動(dòng)失效了,需要重新登錄。
分布式session
SESSIONID僅存儲(chǔ)在單機(jī)服務(wù)器會(huì)有單點(diǎn)問題,一般解決Session在多個(gè)服務(wù)器之間的共享問題有幾種方法:
1、Sesson Replication
也就是每臺(tái)Web服務(wù)器都保存所有用戶的Session數(shù)據(jù),網(wǎng)絡(luò)開銷大,性能低。
2、Sesson Sticky
基于Nginx的sticky模塊,即讓前置負(fù)載均衡器根據(jù)每次請(qǐng)求的ip/cookie來進(jìn)行請(qǐng)求的轉(zhuǎn)發(fā),保證一個(gè)會(huì)話中的每次請(qǐng)求都能落到同一臺(tái)服務(wù)器上面。
缺陷:如果某臺(tái)服務(wù)器宕機(jī),那么它上面存儲(chǔ)的Session數(shù)據(jù)就丟失了,用戶就需要重新進(jìn)行登陸。
負(fù)載均衡器變?yōu)橐粋€(gè)有狀態(tài)的節(jié)點(diǎn),因?yàn)樗枰4鍿ession到具體服務(wù)器的映射,和之前無狀態(tài)的節(jié)點(diǎn)相比,內(nèi)存消耗會(huì)更大,容災(zāi)方面會(huì)更麻煩。
3、Sesson數(shù)據(jù)集群存儲(chǔ)
集中存到外部緩存如Redis中,一般大型系統(tǒng)都會(huì)單獨(dú)部署redis緩存集群。
Session和cookie一樣都有可能遭到跨站請(qǐng)求偽造(CSRF)攻擊,一般可以使用token解決,對(duì)于敏感操作可以雙重驗(yàn)證,如用戶登陸和資金轉(zhuǎn)賬使用獨(dú)立密碼。
Token
Token是身份驗(yàn)證和授權(quán)令牌,具有無狀態(tài)性、跨域支持、安全性等優(yōu)點(diǎn),但需注意安全傳輸、失效管理等問題。Token安全策略包括使用HTTPS、加密存儲(chǔ)、設(shè)置過期時(shí)間等。
常用的jwt?token分為三部分:
1、header:指定簽名算法和令牌類型。
2、payload:也是一個(gè)JSON對(duì)象,用于存放需要傳遞的數(shù)據(jù),例如用戶的信息uid、權(quán)限等,一般還會(huì)攜帶時(shí)間戳,用于防止重放攻擊。
JWT推薦了payload中的7個(gè)可選官方字段:iss (簽發(fā)人), exp (過期時(shí)間), ?aud (接收方)等,另外也可以自己協(xié)商私有字段。
3、Signature:簽名,server根據(jù)header獲取簽名算法,再用公鑰根據(jù)此簽名算法對(duì)head+payload 生成簽名,這樣一個(gè)token就生成了。
客戶端使用token執(zhí)行會(huì)話流程
1、用戶使用用戶名和密碼進(jìn)行登錄,系統(tǒng)驗(yàn)證用戶身份成功后,生成一個(gè)Token并返回給客戶端。
2、客戶端將Token保存在本地,通常是存儲(chǔ)在Cookie或LocalStorage中,在每次發(fā)送請(qǐng)求時(shí)在請(qǐng)求頭帶上token。
3、服務(wù)端驗(yàn)證token,通過后執(zhí)行業(yè)務(wù)操作。
4、如果token非法,返回401給客戶端,客戶端需要執(zhí)行登錄操作。
服務(wù)端如何驗(yàn)證token
這里就要考慮token如何生成。Token既可以使用對(duì)稱加密也可以使用非對(duì)稱加密(JWT)生成。對(duì)稱加密中Token 中通常只有 userId 和 timestamp,客戶端無法解密token也無法往里面添加內(nèi)容。非對(duì)稱加密中客戶端可以使用公鑰解密token獲取用戶信息,也可以使用公鑰打包添加額外信息。
為了安全起見,防止token被攻擊者盜用,token 的有效期不會(huì)設(shè)置的太長,這樣就會(huì)由于 token過期導(dǎo)致用戶需要重新登錄從而生成新的token。服務(wù)端驗(yàn)證token過期會(huì)返回401,前端重定向到登錄頁面。如何才能做到不需要用戶去頻繁的登錄呢,有下面兩種形式。
1、單token方案
2、雙token方案,Refresh Token機(jī)制
Oauth2認(rèn)證規(guī)范中把之前的那個(gè)Token稱為Access Token,業(yè)務(wù)方用這個(gè) Access Token進(jìn)行認(rèn)證鑒權(quán)。而Refresh Token是專門用來在Access Token過期后重新獲取新的Access Token的Token。
Refresh Token的過期時(shí)間一般設(shè)置長一點(diǎn)
比如一天,Access Token的過期時(shí)間設(shè)置短一點(diǎn)比如30分鐘,這樣可以縮短Access Token的過期時(shí)間保證安全,同時(shí)又不會(huì)因?yàn)轭l繁過期重新要求用戶登錄。
Access Token每次訪問都要攜帶,因此更容易被盜??;Refresh Token被盜的風(fēng)險(xiǎn)遠(yuǎn)小于Access Token。
Token失效與管理
Token是由客戶端保存的,一旦服務(wù)器生成,它就一直有效,直到過期。換句話說,服務(wù)器無法主動(dòng)讓一個(gè)Token失效,除非將Token放入“黑名單”中,這樣每次驗(yàn)證Token時(shí)都需要先檢查這個(gè)黑名單。如果Token在黑名單里,就會(huì)被認(rèn)為無效。但問題是,黑名單必須保存在服務(wù)器端,這意味著如果要支持服務(wù)器隨時(shí)“踢掉”用戶,就相當(dāng)于將服務(wù)變成了“有狀態(tài)”的管理,類似于Session模式。
所以,一般情況下,當(dāng)客戶端登出時(shí),最簡單的方法是直接刪除本地存儲(chǔ)的Token,用戶下次登錄時(shí),服務(wù)器會(huì)重新生成一個(gè)新的Token。
對(duì)于大規(guī)模用戶的系統(tǒng),通常會(huì)部署獨(dú)立的認(rèn)證系統(tǒng),并使用如Redis集群這種方式來存儲(chǔ)Session,這樣就能更方便地管理Token的過期和刷新問題。
通常,Token是存放在HTTP請(qǐng)求頭中的Authorization字段,而不是放在Cookie里。這樣做的主要原因是為了解決跨域請(qǐng)求時(shí)無法共享Cookie的問題。對(duì)于跨域問題,通常需要在服務(wù)器端配置允許來自所有域的請(qǐng)求,這可以通過設(shè)置Access-Control-Allow-Origin: *來實(shí)現(xiàn)。
基于token可以實(shí)現(xiàn)SSO單點(diǎn)登錄,只需要各個(gè)服務(wù)器都使用同一個(gè)密鑰對(duì)token進(jìn)行加密解密即可。