參考文章:
基于 Cookie/Session 的認(rèn)證方案
Cookie
- Cookie的工作原理
由于HTTP
是一種無(wú)狀態(tài)的協(xié)議颅悉,服務(wù)器單從網(wǎng)絡(luò)連接上無(wú)從知道客戶身份。怎么辦呢迁匠?就給客戶端們頒發(fā)一個(gè)通行證吧剩瓶,每人一個(gè),無(wú)論誰(shuí)訪問(wèn)都必須攜帶自己通行證城丧。這樣服務(wù)器就能從通行證上確認(rèn)客戶身份了延曙。這就是。
cookie
指的就是在瀏覽器里面存儲(chǔ)的一種數(shù)據(jù)芙贫,僅僅是瀏覽器實(shí)現(xiàn)的一種數(shù)據(jù)存儲(chǔ)功能搂鲫。
cookie
的保存時(shí)間,可以自己在程序中設(shè)置磺平。如果沒(méi)有設(shè)置保存時(shí)間魂仍,應(yīng)該是一關(guān)閉瀏覽器拐辽,cookie
就自動(dòng)消失。
Cookie
實(shí)際上是一小段的文本信息擦酌【阒睿客戶端請(qǐng)求服務(wù)器,如果服務(wù)器需要記錄該用戶狀態(tài)赊舶,就使用response
向客戶端瀏覽器頒發(fā)一個(gè)Cookie
睁搭。客戶端瀏覽器會(huì)把Cookie
保存起來(lái)笼平。當(dāng)瀏覽器再請(qǐng)求該網(wǎng)站時(shí)园骆,瀏覽器把請(qǐng)求的網(wǎng)址連同該Cookie
一同提交給服務(wù)器。服務(wù)器檢查該Cookie
寓调,以此來(lái)辨認(rèn)用戶狀態(tài)锌唾。服務(wù)器還可以根據(jù)需要修改Cookie
的內(nèi)容。
注意:Cookie
功能需要瀏覽器的支持夺英。如果瀏覽器不支持Cookie
(如大部分手機(jī)中的瀏覽器)或者把Cookie
禁用了晌涕,Cookie
功能就會(huì)失效。不同的瀏覽器采用不同的方式保存Cookie
痛悯。IE
瀏覽器會(huì)以文本文件形式保存余黎,一個(gè)文本文件保存一個(gè)Cookie
。
- Cookie的不可跨域名性
Cookie
具有不可跨域名性载萌。根據(jù)Cookie
規(guī)范惧财,瀏覽器訪問(wèn)Google
只會(huì)攜帶Google
的Cookie
,而不會(huì)攜帶Baidu
的Cookie
炒考。瀏覽器判斷一個(gè)網(wǎng)站是否能操作另一個(gè)網(wǎng)站Cookie
的依據(jù)是域名可缚。
Session
Session
是另一種記錄客戶狀態(tài)的機(jī)制,不同的是Cookie
保存在客戶端瀏覽器中斋枢,而Session
保存在服務(wù)器上帘靡。客戶端瀏覽器訪問(wèn)服務(wù)器的時(shí)候瓤帚,服務(wù)器把客戶端信息以某種形式記錄在服務(wù)器上描姚。這就是Session
「甏危客戶端瀏覽器再次訪問(wèn)時(shí)只需要從該Session
中查找該客戶的狀態(tài)就可以了轩勘。
如果說(shuō)Cookie
機(jī)制是通過(guò)檢查客戶身上的“通行證”來(lái)確定客戶身份的話,那么Session
機(jī)制就是通過(guò)檢查服務(wù)器上的“客戶明細(xì)表”來(lái)確認(rèn)客戶身份怯邪。
session
也是類(lèi)似的道理绊寻,服務(wù)器要知道當(dāng)前發(fā)請(qǐng)求給自己的是誰(shuí)。為了做這種區(qū)分,服務(wù)器就要給每個(gè)客戶端分配不同的“身份標(biāo)識(shí)”澄步,然后客戶端每次向服務(wù)器發(fā)請(qǐng)求的時(shí)候冰蘑,都帶上這個(gè)“身份標(biāo)識(shí)”,服務(wù)器就知道這個(gè)請(qǐng)求來(lái)自于誰(shuí)了村缸。對(duì)于瀏覽器客戶端祠肥,大家都默認(rèn)采用 cookie
的方式,保存這個(gè)“身份標(biāo)識(shí)”梯皿。
服務(wù)器使用session
把用戶的信息臨時(shí)保存在了服務(wù)器上仇箱,用戶離開(kāi)網(wǎng)站后session
會(huì)被銷(xiāo)毀。這種用戶信息存儲(chǔ)方式相對(duì)cookie
來(lái)說(shuō)更安东羹。
可是session
有一個(gè)缺陷:如果web
服務(wù)器做了負(fù)載均衡剂桥,那么下一個(gè)操作請(qǐng)求到了另一臺(tái)服務(wù)器的時(shí)候session
會(huì)丟失。
提示:Session
的使用比Cookie
方便属提,但是過(guò)多的Session
存儲(chǔ)在服務(wù)器內(nèi)存中渊额,會(huì)對(duì)服務(wù)器造成壓力。
Cookie與Session的區(qū)別和聯(lián)系
cookie
數(shù)據(jù)存放在客戶的瀏覽器上垒拢,session
數(shù)據(jù)放在服務(wù)器上;cookie
不是很安全火惊,別人可以分析存放在本地的COOKIE
并進(jìn)行COOKIE
欺騙求类,考慮到安全應(yīng)當(dāng)使用session
;session
會(huì)在一定時(shí)間內(nèi)保存在服務(wù)器上屹耐。當(dāng)訪問(wèn)增多尸疆,會(huì)比較占用你服務(wù)器的性能』塘耄考慮到減輕服務(wù)器性能方面寿弱,應(yīng)當(dāng)使用COOKIE
;單個(gè)cookie在客戶端的限制是3K按灶,就是說(shuō)一個(gè)站點(diǎn)在客戶端存放的COOKIE不能超過(guò)3K症革;
Cookie
和Session
的方案雖然分別屬于客戶端和服務(wù)端,但是服務(wù)端的session
的實(shí)現(xiàn)對(duì)客戶端的cookie
有依賴關(guān)系的鸯旁,上面我講到服務(wù)端執(zhí)行session
機(jī)制時(shí)候會(huì)生成session
的id值噪矛,這個(gè)id
值會(huì)發(fā)送給客戶端,客戶端每次請(qǐng)求都會(huì)把這個(gè)id
值放到http
請(qǐng)求的頭部發(fā)送給服務(wù)端铺罢,而這個(gè)id
值在客戶端會(huì)保存下來(lái)艇挨,保存的容器就是cookie
,因此當(dāng)我們完全禁掉瀏覽器的cookie
的時(shí)候韭赘,服務(wù)端的session
也會(huì)不能正常使用缩滨。
基于token的認(rèn)證方式
在大多數(shù)使用Web API
的互聯(lián)網(wǎng)公司中,tokens
是多用戶下處理認(rèn)證的最佳方式。
以下幾點(diǎn)特性會(huì)讓你在程序中使用基于Token的身份驗(yàn)證
1.無(wú)狀態(tài)脉漏、可擴(kuò)展
2.支持移動(dòng)設(shè)備
3.跨程序調(diào)用
4.安全
Token的起源
在介紹基于Token
的身份驗(yàn)證的原理與優(yōu)勢(shì)之前苞冯,不妨先看看之前的認(rèn)證都是怎么做的。
- 基于服務(wù)器的驗(yàn)證
我們都是知道HTTP
協(xié)議是無(wú)狀態(tài)的鸠删,這種無(wú)狀態(tài)意味著程序需要驗(yàn)證每一次請(qǐng)求抱完,從而辨別客戶端的身份。
在這之前刃泡,程序都是通過(guò)在服務(wù)端存儲(chǔ)的登錄信息來(lái)辨別請(qǐng)求的巧娱。這種方式一般都是通過(guò)存儲(chǔ)Session
來(lái)完成。
- 基于服務(wù)器驗(yàn)證方式暴露的一些問(wèn)題
1.Seesion
:每次認(rèn)證用戶發(fā)起請(qǐng)求時(shí)烘贴,服務(wù)器需要去創(chuàng)建一個(gè)記錄來(lái)存儲(chǔ)信息禁添。當(dāng)越來(lái)越多的用戶發(fā)請(qǐng)求時(shí),內(nèi)存的開(kāi)銷(xiāo)也會(huì)不斷增加桨踪。
2.可擴(kuò)展性:在服務(wù)端的內(nèi)存中使用Seesion
存儲(chǔ)登錄信息老翘,伴隨而來(lái)的是可擴(kuò)展性問(wèn)題。
3.CORS
(跨域資源共享):當(dāng)我們需要讓數(shù)據(jù)跨多臺(tái)移動(dòng)設(shè)備上使用時(shí)锻离,跨域資源的共享會(huì)是一個(gè)讓人頭疼的問(wèn)題铺峭。在使用Ajax
抓取另一個(gè)域的資源,就可以會(huì)出現(xiàn)禁止請(qǐng)求的情況汽纠。
4.CSRF
(跨站請(qǐng)求偽造):用戶在訪問(wèn)銀行網(wǎng)站時(shí)卫键,他們很容易受到跨站請(qǐng)求偽造的攻擊,并且能夠被利用其訪問(wèn)其他的網(wǎng)站虱朵。
在這些問(wèn)題中莉炉,可擴(kuò)展行是最突出的。因此我們有必要去尋求一種更有行之有效的方法碴犬。
基于Token的驗(yàn)證原理
基于Token的身份驗(yàn)證是無(wú)狀態(tài)的絮宁,我們不將用戶信息存在服務(wù)器中。這種概念解決了在服務(wù)端存儲(chǔ)信息時(shí)的許多問(wèn)題服协。NoSession
意味著你的程序可以根據(jù)需要去增減機(jī)器绍昂,而不用去擔(dān)心用戶是否登錄。
基于Token的身份驗(yàn)證的過(guò)程如下:
用戶通過(guò)用戶名和密碼發(fā)送請(qǐng)求偿荷。
服務(wù)器端程序驗(yàn)證治专。
3.服務(wù)器端程序返回一個(gè)帶簽名的token
給客戶端。
4.客戶端儲(chǔ)存token
,并且每次訪問(wèn)API
都攜帶Token
到服務(wù)器端的遭顶。
5.服務(wù)端驗(yàn)證token
张峰,校驗(yàn)成功則返回請(qǐng)求數(shù)據(jù),校驗(yàn)失敗則返回錯(cuò)誤碼棒旗。
Tokens的優(yōu)勢(shì)
- 無(wú)狀態(tài)喘批、可擴(kuò)展
在客戶端存儲(chǔ)的Tokens
是無(wú)狀態(tài)的撩荣,并且能夠被擴(kuò)展∪纳睿基于這種無(wú)狀態(tài)和不存儲(chǔ)Session
信息餐曹,負(fù)載負(fù)載均衡器能夠?qū)⒂脩粜畔囊粋€(gè)服務(wù)傳到其他服務(wù)器上。
tokens
自己hold
住了用戶的驗(yàn)證信息敌厘。
- 安全性
請(qǐng)求中發(fā)送token
而不再是發(fā)送cookie
能夠防止CSRF
(跨站請(qǐng)求偽造)台猴。即使在客戶端使用cookie
存儲(chǔ)token
,cookie
也僅僅是一個(gè)存儲(chǔ)機(jī)制而不是用于認(rèn)證俱两。不將信息存儲(chǔ)在Session
中饱狂,讓我們少了對(duì)session
操作。
token
是有時(shí)效的宪彩,一段時(shí)間之后用戶需要重新驗(yàn)證休讳。
- 可擴(kuò)展性
Tokens
能夠創(chuàng)建與其它程序共享權(quán)限的程序。
- 多平臺(tái)跨域
我們提前先來(lái)談?wù)撘幌?code>CORS(跨域資源共享)尿孔,對(duì)應(yīng)用程序和服務(wù)進(jìn)行擴(kuò)展的時(shí)候俊柔,需要介入各種各種的設(shè)備和應(yīng)用程序。
需要設(shè)置有效期嗎活合?
對(duì)于這個(gè)問(wèn)題雏婶,我們不妨先看兩個(gè)例子。一個(gè)例子是登錄密碼白指,一般要求定期改變密碼尚骄,以防止泄漏,所以密碼是有有效期的侵续;另一個(gè)例子是安全證書(shū)。SSL
安全證書(shū)都有有效期憨闰,目的是為了解決吊銷(xiāo)的問(wèn)題状蜗。所以無(wú)論是從安全的角度考慮,還是從吊銷(xiāo)的角度考慮鹉动,Token
都需要設(shè)有效期轧坎。
- 那么有效期多長(zhǎng)合適呢?
只能說(shuō)泽示,根據(jù)系統(tǒng)的安全需要缸血,盡可能的短,但也不能短得離譜
- 然后新問(wèn)題產(chǎn)生了械筛,如果用戶在正常操作的過(guò)程中捎泻,
Token
過(guò)期失效了,要求用戶重新登錄……用戶體驗(yàn)豈不是很糟糕埋哟?
一種方案笆豁,使用 Refresh Token
,它可以避免頻繁的讀寫(xiě)操作。這種方案中闯狱,服務(wù)端不需要刷新 Token
的過(guò)期時(shí)間煞赢,一旦 Token
過(guò)期,就反饋給前端哄孤,前端使用 Refresh Token
申請(qǐng)一個(gè)全新Token
繼續(xù)使用照筑。這種方案中,服務(wù)端只需要在客戶端請(qǐng)求更新 Token
的時(shí)候?qū)?Refresh Token
的有效性進(jìn)行一次檢查瘦陈,大大減少了更新有效期的操作凝危,也就避免了頻繁讀寫(xiě)。當(dāng)然 Refresh Token
也是有有效期的双饥,但是這個(gè)有效期就可以長(zhǎng)一點(diǎn)了媒抠,比如,以天為單位的時(shí)間咏花。
- 時(shí)序圖表示
使用 Token
和 Refresh Token
的時(shí)序圖如下:
1)登錄
2)業(yè)務(wù)請(qǐng)求
3)
Token
過(guò)期趴生,刷新 Token
上面的時(shí)序圖中并未提到
Refresh Token
過(guò)期怎么辦。不過(guò)很顯然昏翰,Refresh Token
既然已經(jīng)過(guò)期苍匆,就該要求用戶重新登錄了。
項(xiàng)目中使用token總結(jié)
使用基于 Token
的身份驗(yàn)證方法棚菊,在服務(wù)端不需要存儲(chǔ)用戶的登錄記錄浸踩。大概的流程是這樣的:
1.前端使用用戶名跟密碼請(qǐng)求首次登錄
2.后服務(wù)端收到請(qǐng)求,去驗(yàn)證用戶名與密碼是否正確
3.驗(yàn)證成功后统求,服務(wù)端會(huì)根據(jù)用戶id
检碗、用戶名、定義好的秘鑰码邻、過(guò)期時(shí)間生成一個(gè) Token
折剃,再把這個(gè) Token
發(fā)送給前端
4.前端收到 返回的Token
,把它存儲(chǔ)起來(lái)像屋,比如放在 Cookie
里或者 Local Storage
里
export interface User {
token: string;
userInfo: UserInfo | any;
companyInfo: CompanyInfo | any;
resources?: string[];
}
save(key: string, value: any, storageType ?: StorageType) {
return this.storageService.put(
{
pool: key,
key: 'chris-app',
storageType: StorageType.localStorage
},
value
);
}
this.storageService.save(CACHE_USER_KEY, user);
5.前端每次路由跳轉(zhuǎn)怕犁,判斷 localStroage
有無(wú) token
,沒(méi)有則跳轉(zhuǎn)到登錄頁(yè)己莺。有則請(qǐng)求獲取用戶信息奏甫,改變登錄狀態(tài);
6.前端每次向服務(wù)端請(qǐng)求資源的時(shí)候需要在請(qǐng)求頭里攜帶服務(wù)端簽發(fā)的Token
HttpInterceptor => headers = headers.set('token', this.authService.getToken());
7.服務(wù)端收到請(qǐng)求凌受,然后去驗(yàn)證前端請(qǐng)求里面帶著的 Token
阵子。沒(méi)有或者 token
過(guò)期,返回401
胜蛉。如果驗(yàn)證成功款筑,就向前端返回請(qǐng)求的數(shù)據(jù)智蝠。
8.前端得到 401
狀態(tài)碼,重定向到登錄頁(yè)面奈梳。
HttpInterceptor =>
401: '用戶登陸狀態(tài)失效杈湾,請(qǐng)重新登陸。'