什么是認(rèn)證(Authentication)
通俗地講,認(rèn)證就是驗(yàn)證當(dāng)前用戶的身份是否合法的過(guò)程。比如指紋打卡,當(dāng)你的指紋和系統(tǒng)里錄入的指紋相匹配時(shí),就打卡成功。
像用戶名密碼登錄、郵箱發(fā)送登錄鏈接、手機(jī)接收驗(yàn)證碼等等都屬于互聯(lián)網(wǎng)中的常見(jiàn)認(rèn)證方式,只要你能收到驗(yàn)證碼,就默認(rèn)你是賬號(hào)的主人。認(rèn)證主要是為了保護(hù)系統(tǒng)的隱私數(shù)據(jù)與資源。
而一旦認(rèn)證通過(guò),那么一定時(shí)間內(nèi)就不用再認(rèn)證了(銀行轉(zhuǎn)賬等高度敏感操作除外),這時(shí)就可以將用戶信息保存在會(huì)話中。會(huì)話是系統(tǒng)為了保存當(dāng)前用戶的登錄狀態(tài)所提供的機(jī)制,實(shí)現(xiàn)方式通常是 Session 或 Token,后面說(shuō)。
什么是授權(quán)(Authorization)
簡(jiǎn)單來(lái)講就是誰(shuí)(who)對(duì)什么(what)進(jìn)行了什么操作(how),和認(rèn)證不同,認(rèn)證是確認(rèn)用戶的合法性,以及讓服務(wù)端知道你是誰(shuí),而授權(quán)則是為了更細(xì)粒度地對(duì)資源進(jìn)行權(quán)限上的劃分。所以授權(quán)是在認(rèn)證通過(guò)后,控制不同的用戶訪問(wèn)不同的資源。
并且授權(quán)是雙向的,可以是用戶給服務(wù)端授權(quán),也可以是服務(wù)端給用戶授權(quán)。
用戶給服務(wù)端授權(quán):比如你在安裝手機(jī)應(yīng)用的時(shí)候,APP 會(huì)詢問(wèn)是否允許授予權(quán)限(訪問(wèn)相冊(cè)、地理位置等權(quán)限);你在登錄微信小程序時(shí),小程序會(huì)詢問(wèn)是否允許授予權(quán)限(獲取昵稱、頭像、地區(qū)、性別等個(gè)人信息)。
服務(wù)端給用戶授權(quán):比如你想追一個(gè)很火的劇,但被告知必須是 VIP 才能觀看,于是你充錢成為了 VIP,那么服務(wù)端便會(huì)給你授予觀看該?。ㄔL問(wèn)該資源)的權(quán)限。
而實(shí)現(xiàn)授權(quán)的方式業(yè)界通常使用 RBAC,這個(gè) R 有兩種解讀:第一種是基于角色的訪問(wèn)控制,即 Role-Based Access Control;第二種是基于資源(權(quán)限)的訪問(wèn)控制,系統(tǒng)設(shè)計(jì)時(shí)定義好某項(xiàng)操作的權(quán)限標(biāo)識(shí),即 Resource-Based Access Control。
什么是憑證(Credentials)
實(shí)現(xiàn)認(rèn)證和授權(quán)的前提是需要一種媒介(證書(shū))來(lái)標(biāo)記訪問(wèn)者的身份。
例如在戰(zhàn)國(guó)時(shí)期,商鞅變法,發(fā)明了照身帖。照身帖由官府發(fā)放,是一塊打磨的光滑細(xì)密的竹板,上面刻有持有人的頭像和籍貫信息。國(guó)人必須持有,如若沒(méi)有就被認(rèn)為是黑戶,或者間諜之類的。在現(xiàn)實(shí)生活中,每個(gè)人都會(huì)有一張專屬的居民身份證,是用于證明持有人身份的一種法定證件。通過(guò)身份證,我們可以辦理手機(jī)卡/銀行卡/個(gè)人貸款/交通出行等等,這就是認(rèn)證的憑證。
在互聯(lián)網(wǎng)應(yīng)用中,一般網(wǎng)站會(huì)有兩種模式,游客模式和登錄模式。游客模式下,可以正常瀏覽網(wǎng)站上面的文章,一旦想要點(diǎn)贊/收藏/分享文章,就需要登錄或者注冊(cè)賬號(hào)。
當(dāng)用戶登錄成功后,服務(wù)器會(huì)給該用戶使用的瀏覽器頒發(fā)一個(gè)令牌(token),這個(gè)令牌用來(lái)表明你的身份。每次瀏覽器發(fā)送請(qǐng)求時(shí)會(huì)帶上這個(gè)令牌,就可以使用游客模式下無(wú)法使用的功能。
什么是 Cookie 和 Session
HTTP 是無(wú)狀態(tài)的協(xié)議,對(duì)于事務(wù)處理沒(méi)有記憶能力,客戶端和服務(wù)端完成會(huì)話時(shí),服務(wù)端不會(huì)保存任何會(huì)話信息。也就是說(shuō)每個(gè)請(qǐng)求都是完全獨(dú)立的,服務(wù)端無(wú)法確認(rèn)當(dāng)前訪問(wèn)者的身份信息,無(wú)法分辨上一次請(qǐng)求的發(fā)送者和這一次的發(fā)送者是不是同一個(gè)人。
所以服務(wù)器與瀏覽器為了進(jìn)行會(huì)話跟蹤(知道是誰(shuí)在訪問(wèn)),就必須主動(dòng)地去維護(hù)一個(gè)狀態(tài),這個(gè)狀態(tài)用于告知服務(wù)端前后兩個(gè)請(qǐng)求是否來(lái)自同一瀏覽器,而這個(gè)狀態(tài)就需要 Cookie 和 Session 共同去實(shí)現(xiàn)。
那 Cookie 是什么呢?Cookie 本質(zhì)上就是服務(wù)器發(fā)送給用戶瀏覽器的一小塊數(shù)據(jù),一旦用戶認(rèn)證成功,那么瀏覽器就會(huì)收到服務(wù)器發(fā)送的 Cookie,然后將其并保存在本地。當(dāng)瀏覽器下次再向同一服務(wù)器發(fā)起請(qǐng)求時(shí),會(huì)攜帶該 Cookie(放在請(qǐng)求頭中),并發(fā)送給服務(wù)器。服務(wù)器拿到 Cookie 后,會(huì)檢測(cè) Cookie 是否過(guò)期,如果沒(méi)有過(guò)期再通過(guò) Cookie 判斷用戶的信息。
注意:Cookie 是不可跨域的,每個(gè) Cookie 都會(huì)綁定單一的域名,無(wú)法在別的域名下獲取使用。一級(jí)域名和二級(jí)域名之間是允許共享使用的(靠的是 domain)。
以上就是 Cookie,但是問(wèn)題來(lái)了,我們能把一些敏感信息存在 Cookie 里面嗎?把用戶名、密碼都放在 Cookie 里面,然后瀏覽器發(fā)請(qǐng)求的時(shí)候再讀取 Cookie 里面的內(nèi)容,驗(yàn)證用戶的合法性,可以這么做嗎?顯然是不能的,因?yàn)檫@樣太不安全了,所以就有了 Session。
注意:Session 它并不是真實(shí)存在的,而是我們引入的一個(gè)抽象概念,它的實(shí)現(xiàn)依賴于 Cookie。因?yàn)槲覀儾荒馨衙舾行畔⒎旁?Cookie 里面,所以最好的方式就是直接放在服務(wù)端,用戶的這些敏感信息可以看成是一個(gè) Session,我們可以用一個(gè)映射保存起來(lái)。然后為每一個(gè) Session 都創(chuàng)建一個(gè)與之關(guān)聯(lián)的 SessionID,舉個(gè)例子:
{
"不重復(fù)的 sessionID-1": {"user_name": "xx1",
"password": "yy1",
"phone": 135...},
"不重復(fù)的 sessionID-2": {"user_name": "xx2",
"password": "yy2",
"phone": 136...},
...
}
使用映射保存是最方便的,這些敏感信息我們存在服務(wù)端,然后將 SessionID 返回即可,那么這個(gè) SessionID 放在哪里呢?沒(méi)錯(cuò),放在 Cookie 中,所以 Session 的實(shí)現(xiàn)要依賴于 Cookie。我們以用戶登錄來(lái)模擬一下整個(gè)過(guò)程:
用戶第一次請(qǐng)求服務(wù)器的時(shí)候,服務(wù)器根據(jù)用戶提交的相關(guān)信息,創(chuàng)建對(duì)應(yīng)的 Session;
請(qǐng)求返回時(shí)將此 Session 的唯一標(biāo)識(shí)信息 SessionID 返回給瀏覽器;
瀏覽器接收到服務(wù)器返回的 SessionID 信息后,會(huì)將此信息存入到 Cookie 中,同時(shí) Cookie 記錄此 SessionID 屬于哪個(gè)域名;
當(dāng)用戶第二次訪問(wèn)服務(wù)器的時(shí)候,瀏覽器會(huì)自動(dòng)判斷此域名下是否存在 Cookie 信息,如果存在,會(huì)將 Cookie 信息也發(fā)送給服務(wù)端;服務(wù)端會(huì)從 Cookie 中獲取 SessionID,再根據(jù) SessionID 查找對(duì)應(yīng)的 Session 信息,如果沒(méi)有找到說(shuō)明用戶沒(méi)有登錄或者登錄失效,如果找到 Session 證明用戶已經(jīng)登錄可執(zhí)行后面操作;
根據(jù)以上流程可知,SessionID 是連接 Cookie 和 Session 的一道橋梁,大部分系統(tǒng)也是根據(jù)此原理來(lái)「驗(yàn)證用戶登錄狀態(tài)」。
Session 的痛點(diǎn)
通過(guò) Cookie + Session 的方式雖然能實(shí)現(xiàn)認(rèn)證, 但是存在一個(gè)問(wèn)題,上述情況能正常工作是因?yàn)槲覀兗僭O(shè) Server 是單機(jī)的。但實(shí)際在生產(chǎn)上,為了保障高可用,一般服務(wù)器至少需要兩臺(tái)機(jī)器,通過(guò)負(fù)載均衡的方式來(lái)決定請(qǐng)求到底該打到哪臺(tái)機(jī)器上。
假設(shè)第一次的登錄請(qǐng)求打到了 A 機(jī)器,A 機(jī)器生成了 Session 并在 Cookie 里添加 SessionId 返回給了瀏覽器。
那么問(wèn)題來(lái)了:下次如果請(qǐng)求(比如添加購(gòu)物車)打到了 B 或者 C,由于 Session 是在 A 機(jī)器生成的,此時(shí)的 B、C 是找不到 Session 的,因此就會(huì)發(fā)生無(wú)法添加購(gòu)物車的錯(cuò)誤,就得重新登錄了,此時(shí)該怎么辦呢?解決方式主要有以下三種:
Session 復(fù)制
A 生成 Session 后復(fù)制到 B、C,這樣每臺(tái)機(jī)器都有一份 Session,無(wú)論添加購(gòu)物車的請(qǐng)求打到哪臺(tái)機(jī)器,由于 Session 都能找到,所以不會(huì)有問(wèn)題。
這種方式雖然可行,但缺點(diǎn)也很明顯:
同樣的一份 Session 保存了多份,數(shù)據(jù)冗余;
如果節(jié)點(diǎn)少還好,但如果節(jié)點(diǎn)多的話,特別是像阿里,微信這種由于 DAU 上億,可能需要部署成千上萬(wàn)臺(tái)機(jī)器,這樣由于節(jié)點(diǎn)增多導(dǎo)致復(fù)制造成的性能消耗也會(huì)很大;
Session 粘連
這種方式是讓每個(gè)客戶端請(qǐng)求只打到固定的一臺(tái)機(jī)器上,比如瀏覽器登錄請(qǐng)求打到 A 機(jī)器后,后續(xù)所有的添加購(gòu)物車的請(qǐng)求也都打到 A 機(jī)器上。Nginx 的 sticky 模塊可以支持這種方式,支持按 ip 或 cookie 粘連等等,比如按 ip 粘連:
這樣的話每個(gè) client 請(qǐng)求到達(dá) Nginx 后,只要它的 ip 不變,根據(jù) ip hash 算出來(lái)的值就會(huì)打到固定的機(jī)器上,也就不存在 Session 找不到的問(wèn)題了。
當(dāng)然這種方式的缺點(diǎn)也是很明顯,對(duì)應(yīng)的機(jī)器掛了怎么辦?此外,這種方式還會(huì)造成一個(gè)問(wèn)題,那就是 Nginx 會(huì)變得有狀態(tài)。
Session 共享
這種方式也是目前各大公司普遍采用的方案,將 Session 保存在 Redis,Memcached 等中間件中,請(qǐng)求到來(lái)時(shí),各個(gè)機(jī)器去這些中間件取一下 Session 即可。
缺點(diǎn)其實(shí)也不難發(fā)現(xiàn),就是每個(gè)請(qǐng)求都要去 Redis 取一下 Session,多了一次內(nèi)部連接,消耗了一點(diǎn)性能。另外為了保證 Redis 的高可用,必須做集群。當(dāng)然了,對(duì)于大部分公司來(lái)說(shuō),Redis 集群基本都會(huì)部署,所以這方案可以說(shuō)是首選了。
什么是 Token(令牌)
通過(guò)上文分析,我們知道通過(guò)在服務(wù)端共享 Session 的方式可以完成用戶的身份定位,但是背后也有一個(gè)小小的瑕疵:搞個(gè)校驗(yàn)機(jī)制我還得搭個(gè) Redis 集群?
大廠確實(shí) Redis 用得比較普遍,但對(duì)于小廠來(lái)說(shuō)可能它的業(yè)務(wù)量還未達(dá)到用 Redis 的程度,所以有沒(méi)有其它不用 Server 存儲(chǔ) Session 的用戶身份校驗(yàn)機(jī)制呢,答案是使用 Token。
基于 Token 的鑒權(quán)機(jī)制類似于 http 協(xié)議,也是無(wú)狀態(tài)的,它不需要在服務(wù)端保留用戶的認(rèn)證信息或者會(huì)話信息。這就意味著基于 Token 認(rèn)證機(jī)制的應(yīng)用不需要去考慮用戶在哪一臺(tái)服務(wù)器登錄了,這就為應(yīng)用的擴(kuò)展提供了便利。
用戶使用用戶名密碼來(lái)請(qǐng)求服務(wù)器;
服務(wù)器驗(yàn)證用戶的信息;
認(rèn)證通過(guò)后,發(fā)送給用戶一個(gè) Token;
客戶端存儲(chǔ) Token,并在后續(xù)的請(qǐng)求中附上這個(gè) Token 值;
服務(wù)端驗(yàn)證 Token 值,并返回?cái)?shù)據(jù);
這個(gè) Token 必須要在每次請(qǐng)求時(shí)傳遞給服務(wù)端,它應(yīng)該保存在請(qǐng)求頭里。 如果保存在 Cookie 里面,服務(wù)端還要支持 CORS(跨來(lái)源資源共享)策略。
而基于 Token 認(rèn)證一般采用 JWT,它由三段信息構(gòu)成,將這三段信息用 . 連接起來(lái)就構(gòu)成了 JWT 字符串。就像如下這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分我們稱之為頭部(Header)、第二部分我們稱之為載荷(Payload)、第三部分我們稱之為簽名(Signature),下面來(lái)分別介紹。
Header
JWT 的頭部承載兩部分信息:
聲明類型,這里是 JWT;
聲明加密的算法 通常直接使用 HMAC SHA256
{
'typ': 'JWT',
'alg': 'HS256'
}
然后將頭部進(jìn)行 base64 編碼(可以對(duì)稱解碼),構(gòu)成了第一部分。
import base64
import orjson
header = {
'typ': 'JWT',
'alg': 'HS256'
}
print(
base64.urlsafe_b64encode(orjson.dumps(header)).decode()
) # eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload
載荷就是存放有效信息的地方,說(shuō)人話就是用戶的一些信息,里面可以放任意內(nèi)容。我們同樣采用 base64 編碼,就得到了 JWT 的第二部分。注意:不要放一些敏感內(nèi)容,因?yàn)?base64 是可以對(duì)稱解碼的。
import base64
import orjson
payload = {
'name': '最上川',
'phone': '12345678901',
'user_id': '001',
'admin': False,
}
print(
base64.urlsafe_b64encode(orjson.dumps(payload)).decode()
) # eyJuYW1lIjoi5pyA5LiK5bedIiwicGhvbmUiOiIxMjM0NT......
除了我們添加的一些聲明之外,JWT 還提供了一些標(biāo)準(zhǔn)聲明(建議但不強(qiáng)制使用):
iss:JWT 簽發(fā)者;
sub:JWT 所面向的用戶;
aud:接收 JWT 的一方;
exp:JWT 的過(guò)期時(shí)間,這個(gè)過(guò)期時(shí)間必須要大于簽發(fā)時(shí)間;
nbf:定義在什么時(shí)間之前,該 JWT 都是不可用的;
iat:JWT 的簽發(fā)時(shí)間;
jti:JWT的唯一身份標(biāo)識(shí),主要用來(lái)作為一次性 Token,從而回避重放攻擊;
Signature
JWT 的第三部分是一個(gè)簽名信息,這個(gè)簽名信息由三部分組成:
base64 之后的 Header;
base64 之后的 Payload;
secret;
首先將 base64 編碼后的 Header 和 base64 編碼后的 Payload 使用 . 連接起來(lái),然后通過(guò) Header 中聲明的加密方式進(jìn)行加鹽,再進(jìn)行 base64 編碼,然后就構(gòu)成了 JWT 的第三部分。我們舉個(gè)例子:
import base64
import hmac
import orjson
header = {
'typ': 'JWT',
'alg': 'HS256'
}
payload = {
'name': '最上川',
'phone': '12345678901',
'user_id': '001',
'admin': False,
}
# 得到 JWT 的第一部分,和 JWT 的第二部分
jwt_one = base64.urlsafe_b64encode(
orjson.dumps(header))
jwt_two = base64.urlsafe_b64encode(
orjson.dumps(payload))
# 計(jì)算 JWT 第三部分
# 指定一個(gè)密鑰,這里就叫 b"secret"
jwt_three = base64.urlsafe_b64encode(
hmac.new(b"secret", jwt_one + b"." + jwt_two, "SHA256").digest()
).decode()
print(jwt_three) # 4oRUd9Diyp_C0W_LD1of0MxzIuRXvfSroUR_VhP-dSQ=
再將這三部分用 . 連接起來(lái),得到一個(gè)字符串,即可構(gòu)成最終的 JWT。當(dāng)用戶登錄成功之后,服務(wù)端會(huì)生成 JWT 然后交給瀏覽器保存。
后續(xù)當(dāng) Server 收到瀏覽器傳過(guò)來(lái)的 Token 時(shí),它會(huì)首先取出 Token 中的 Header + Payload,根據(jù)密鑰生成簽名,然后再與 Token 中的簽名比對(duì),如果成功則說(shuō)明簽名是合法的,即 Token 是合法的。然后根據(jù) Payload 中保存的信息,我們便可得知該用戶是誰(shuí),避免了像 Session 那樣要從 Redis 獲取的開(kāi)銷。
你會(huì)發(fā)現(xiàn)這種方式確實(shí)很妙,只要 Server 保證密鑰不泄露,那么生成的 Token 就是安全的。因?yàn)閭卧?Token 的話在簽名驗(yàn)證環(huán)節(jié)是無(wú)法通過(guò)的,可以判定出 Token 的合法性。
注意:secret(密鑰)是保存在服務(wù)器端的,JWT 的生成也是在服務(wù)器端的,Secret 就是用來(lái)進(jìn)行 JWT 的簽發(fā)和 JWT 的驗(yàn)證。所以,它就是你服務(wù)端的私鑰,在任何場(chǎng)景都不應(yīng)該流露出去。否則一旦客戶端得知這個(gè) Secret,那就意味著客戶端可以自我簽發(fā) JWT 了。
通過(guò)這種方式就有效地避免了 Token 必須保存在 Server 的弊端,實(shí)現(xiàn)了分布式存儲(chǔ)。不過(guò)需要注意的是,Token 一旦由 Server 生成,直到過(guò)期之前它都是有效的,并且無(wú)法讓 Token 失效,除非在 Server 端為 Token 設(shè)立一個(gè)黑名單,在校驗(yàn) Token 前先過(guò)一遍此黑名單,如果在黑名單里則此 Token 失效。
但一旦這樣做的話,那就意味著黑名單必須保存在 Server,這又回到了 Session 的模式,那直接用 Session 不香嗎。所以一般的做法是當(dāng)客戶端登出,或者 Token 過(guò)期時(shí),直接在本地移除 Token,下次登錄再重新生成 Token。而判斷 Token 是否過(guò)期,可以在生成 JWT 的時(shí)候,在里面加上時(shí)間信息,拿到 Token 時(shí)再進(jìn)行比對(duì)。
另外需要注意的是 Token 一般是放在 Header 的 Authorization 自定義頭里,不是放在 Cookie 里,這主要是為了解決跨域不能共享 Cookie 的問(wèn)題。
# 一般會(huì)加上一個(gè) Bearer 標(biāo)注
Authorization: Bearer <Token>
總結(jié):Token 解決什么問(wèn)題(為什么要用 Token)?
1)完全由應(yīng)用管理,可以避開(kāi)同源策略;
2)支持跨域訪問(wèn),Cookie 不支持。Cookie 跨站是不能共享的,這樣的話如果你要實(shí)現(xiàn)多應(yīng)用(多系統(tǒng))的單點(diǎn)登錄(SSO),使用 Cookie 來(lái)做的話就很困難了。但如果用 Token 來(lái)實(shí)現(xiàn) SSO 會(huì)非常簡(jiǎn)單,只要在 header 中的 Authorize 字段(或其他自定義)加上 Token 即可完成所有跨域站點(diǎn)的認(rèn)證;
3)token是無(wú)狀態(tài)的,可以在多個(gè)服務(wù)器間共享;
4)Token 可以避免 CSRF 攻擊(跨站請(qǐng)求攻擊);
5)易于擴(kuò)展,在移動(dòng)端的原生請(qǐng)求是沒(méi)有 Cookie 之說(shuō)的,而 Sessionid 依賴于 Cookie,所以 SessionID 就不能用 Cookie 來(lái)傳了。如果用 Token 的話,由于它是隨著 header 的 Authorization 傳過(guò)來(lái)的,也就不存在此問(wèn)題,換句話說(shuō) Token 天生支持移動(dòng)平臺(tái),可擴(kuò)展性好;
擴(kuò)展閱讀
下面再來(lái)補(bǔ)充一些額外內(nèi)容。
什么是 CSRF
攻擊者通過(guò)一些技術(shù)手段欺騙用戶的瀏覽器去訪問(wèn)一個(gè)自己曾經(jīng)認(rèn)證過(guò)的網(wǎng)站并運(yùn)行一些操作(如發(fā)郵件,發(fā)消息,甚至財(cái)產(chǎn)操作如轉(zhuǎn)賬和購(gòu)買商品)。由于瀏覽器曾經(jīng)認(rèn)證過(guò)(Cookie 里面帶有 SessionId 等身份認(rèn)證的信息),所以被訪問(wèn)的網(wǎng)站會(huì)認(rèn)為是真正的用戶而去執(zhí)行操作。
如果正常的用戶誤點(diǎn)了惡意鏈接,由于相同域名的請(qǐng)求會(huì)自動(dòng)帶上 Cookie,而 Cookie 里帶有正常登錄用戶的 Sessionid,類似上面這樣的轉(zhuǎn)賬操作在 Server 就會(huì)成功,會(huì)造成極大的安全風(fēng)險(xiǎn)。
就連 Google,都因?yàn)?CSRF 而吃過(guò)嚴(yán)重的虧。
CSRF 攻擊的根本原因在于對(duì)于同樣域名的每個(gè)請(qǐng)求來(lái)說(shuō),它的 Cookie 都會(huì)被自動(dòng)帶上,這個(gè)是瀏覽器的機(jī)制決定的。至于完成一次 CSRF 攻擊需要兩個(gè)步驟:
1. 首先登錄了一個(gè)正常的網(wǎng)站 A,并且在本地生成了 Cookie;
2. 在 Cookie 有效時(shí)間內(nèi),訪問(wèn)了危險(xiǎn)網(wǎng)站 B(就獲取了身份信息);
如果訪問(wèn)完正常網(wǎng)站之后,關(guān)閉瀏覽器呢?即使關(guān)閉瀏覽器,Cookie 也不保證一定立即失效,而且關(guān)閉瀏覽器并不能結(jié)束會(huì)話,Session 的生命周期跟這些都沒(méi)關(guān)系。
什么是同源策略
如果兩個(gè) URL 的協(xié)議、域名和端口號(hào)都相同,那么就是同源的;而有一個(gè)不同,那么就是非同源的。不同源的客戶端腳本在沒(méi)有明確授權(quán)的情況下,不準(zhǔn)讀寫(xiě)對(duì)方的資源。
同源策略是由 Netscape 提出的著名安全策略,是瀏覽器最核心、基本的安全功能,它限制了一個(gè)源中的腳本與來(lái)自其它源中的資源之間的交互方式。
什么是跨域?如何解決
當(dāng)瀏覽器執(zhí)行 JS 腳本時(shí)會(huì)檢查當(dāng)前網(wǎng)頁(yè)的接口和請(qǐng)求的接口是否同源,只有同源的腳本才會(huì)執(zhí)行,如果不同源即為跨域。它是由「瀏覽器的同源策略」造成的,是瀏覽器對(duì) JavaScript 實(shí)施的安全限制。
那么該如何解決呢?
1)Nginx (靜態(tài)服務(wù)器)反向代理解決跨域(前端常用),客戶端訪問(wèn)代理服務(wù)器,代理服務(wù)器再去目標(biāo)服務(wù)器拿數(shù)據(jù),然后返回給客戶端。由于服務(wù)器之間不存在跨域問(wèn)題,所以瀏覽器是允許的。
2)jsonp:通常為了減輕 web 服務(wù)器的負(fù)載,我們把 js、css、圖片等靜態(tài)資源分離到另一臺(tái)獨(dú)立域名的服務(wù)器上,在 html 頁(yè)面中再通過(guò)相應(yīng)的標(biāo)簽從不同域名下加載靜態(tài)資源,而被瀏覽器允許。
3)服務(wù)端在返回的時(shí)候添加幾個(gè)特殊的響應(yīng)頭,瀏覽器在看到這幾個(gè)響應(yīng)頭的時(shí)候,知道服務(wù)端允許跨域,那么會(huì)允許客戶端的這次請(qǐng)求。而響應(yīng)頭如下:
# 允許的源地址
Access-Control-Allow-Origin: *
# 允許的請(qǐng)求頭
Access-Control-Allow-Headers: *
# 允許的請(qǐng)求方法
Access-Control-Allow-Methods: *
為什么 Token 易于擴(kuò)展
比如有使用了「負(fù)載均衡」的多臺(tái)服務(wù)器,第一次登錄請(qǐng)求轉(zhuǎn)發(fā)到了 A,然后 A 中的 Seesion 緩存了用戶的登錄信息。但之后的操作轉(zhuǎn)發(fā)到了 B,這時(shí)候就丟失了登錄狀態(tài),會(huì)再次讓用戶重新登陸。當(dāng)然可以通過(guò)共享 Session 的方式,但 Token 只需要所有的服務(wù)器使用相同的解密手段即可。
無(wú)狀態(tài)
服務(wù)端不保存客戶端請(qǐng)求者的任何信息,客戶端每次請(qǐng)求必須自備描述信息,通過(guò)這些信息來(lái)識(shí)別客戶端身份。服務(wù)端只需要確認(rèn)該 Token是否是自己親自簽發(fā)即可,簽發(fā)和驗(yàn)證都在服務(wù)端進(jìn)行。
什么是單點(diǎn)登錄
所謂單點(diǎn)登錄,是指在多個(gè)應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問(wèn)所有相互信任的應(yīng)用系統(tǒng)。
Token 的缺點(diǎn)
那有人就問(wèn)了,既然 Token 這么好,那為什么各個(gè)大公司幾乎都采用共享 Session 的方式呢。原因是 Token 有以下兩點(diǎn)劣勢(shì):
1)Token 太長(zhǎng)了:Token 是 Header, Payload, Signature 編碼后的樣式,所以一般要比 SessionId 長(zhǎng)很多。如果你在 Token 中存儲(chǔ)的信息越多,那么 Token 本身也會(huì)越長(zhǎng),這樣的話由于你每次請(qǐng)求都會(huì)帶上 Token,那么對(duì)請(qǐng)求來(lái)說(shuō)是個(gè)不小的負(fù)擔(dān);
2)不太安全:網(wǎng)上很多文章說(shuō) Token 更安全,其實(shí)不然。細(xì)心的你可能發(fā)現(xiàn)了,我們說(shuō) Token 是存儲(chǔ)在瀏覽器的,再細(xì)問(wèn),存在瀏覽器的哪里呢?首先不能放在 Cookie 里,那就只好放在 local storage 里,這樣會(huì)造成安全隱患。因?yàn)?local storage 這類的本地存儲(chǔ)是可以被 JS 直接讀取的,另外上面也提到,Token 一旦生成無(wú)法讓其失效,必須等到過(guò)期才行。這樣的話即便服務(wù)端檢測(cè)到了一個(gè)安全威脅,也無(wú)法使相關(guān)的 Token 失效。
所以 Token 更適合一次性的命令認(rèn)證,設(shè)置一個(gè)比較短的有效期。
其實(shí)不管是 Cookie 還是 Token,從存儲(chǔ)角度來(lái)看其實(shí)都不安全(實(shí)際上防護(hù) CSRF 攻擊的正確方式是用 CSRF Token),都有暴露的風(fēng)險(xiǎn)。我們所說(shuō)的安全更多的是強(qiáng)調(diào)傳輸中的安全,可以用 HTTPS 協(xié)議來(lái)傳輸, 這樣的話請(qǐng)求頭都能被加密,也就保證了傳輸中的安全。
當(dāng)然我們把 Cookie 和 Token 放在一起比較本身就不合理,一個(gè)是存儲(chǔ)方式,一個(gè)是驗(yàn)證方式,正確的比較應(yīng)該是 Session 和 Token。
Token 和 Session 的區(qū)別
Token 和 Session 其實(shí)都是為了身份驗(yàn)證,Session 一般翻譯為會(huì)話,而 Token 更多的時(shí)候是翻譯為令牌;Session 和 Token 都是有過(guò)期時(shí)間一說(shuō),都需要去管理過(guò)期時(shí)間。
1)Session 是一種「記錄服務(wù)器和客戶端會(huì)話狀態(tài)的機(jī)制」,使服務(wù)端有狀態(tài)化,可以保存會(huì)話信息(一般保存在緩存中)。而 Token 是「令牌」,是訪問(wèn)資源接口(API)時(shí)所需要的資源憑證,Token 使服務(wù)端無(wú)狀態(tài)化,不會(huì)存儲(chǔ)會(huì)話信息;
2)其實(shí) Token 與 Session 的區(qū)別就是一個(gè)時(shí)間與空間的博弈問(wèn)題,Session 是空間換時(shí)間,而 Token 是時(shí)間換空間,兩者的選擇要看具體情況而定。
雖然確實(shí)都是「客戶端記錄,每次訪問(wèn)攜帶」,但 Token 很容易設(shè)計(jì)為自包含的,也就是說(shuō),后端不需要記錄什么東西,每次一個(gè)無(wú)狀態(tài)請(qǐng)求,每次解密驗(yàn)證,每次當(dāng)場(chǎng)得出合法/非法的結(jié)論。這一切判斷依據(jù),除了固化在 CS 兩端的一些邏輯之外,整個(gè)信息是自包含的,這才是真正的無(wú)狀態(tài)。
而 Session 需要依賴一段隨機(jī)字符串作為 SessionID,每次請(qǐng)求都要去緩存中檢索 ID 的有效性。但這存在風(fēng)險(xiǎn),如果服務(wù)器重啟導(dǎo)致內(nèi)存里的 Session 沒(méi)了怎么辦?萬(wàn)一 Redis 服務(wù)器掛了怎么辦?所以此時(shí)會(huì)使得服務(wù)端變得有狀態(tài)。
3)所謂 Session 認(rèn)證只是簡(jiǎn)單的把 User 信息存儲(chǔ)到 Session 里,因?yàn)?SessionID 的不可預(yù)測(cè)性,暫且認(rèn)為是安全的。
而 Token ,如果指的是 OAuth Token 或類似的機(jī)制的話,提供的是認(rèn)證和授權(quán) 。認(rèn)證是針對(duì)用戶,授權(quán)是針對(duì) App,其目的是讓某 App 有權(quán)利訪問(wèn)某用戶的信息。這里的 Token 是唯一的,不可以轉(zhuǎn)移到其它 App上,也不可以轉(zhuǎn)到其它用戶上。
Session 只提供一種簡(jiǎn)單的認(rèn)證,只要有此 SessionID ,即認(rèn)為有此 User 的全部權(quán)利,因此需要嚴(yán)格保密,這個(gè)數(shù)據(jù)應(yīng)該只保存在站方,不應(yīng)該共享給其它網(wǎng)站或者第三方 App。
所以簡(jiǎn)單來(lái)說(shuō):如果你的用戶數(shù)據(jù)可能需要和第三方共享,或者允許第三方調(diào)用 API 接口,用 Token ;如果只是自己的網(wǎng)站,自己的 App,用什么就無(wú)所謂了。
常見(jiàn)的前后端鑒權(quán)方式有哪些
Session-Cookie;
Token 驗(yàn)證(包括 JWT,SSO);
OAuth2.0(開(kāi)放授權(quán));
本文深度參考自
https://www.jianshu.com/p/cab856c32222
聯(lián)系客服