關(guān)于鑒權(quán),看懂這篇就夠了
劉妮萍: 微醫(yī)前端技術(shù)部前端工程師。養(yǎng)魚養(yǎng)花養(yǎng)狗,熬夜蹦迪喝酒。出走半生,歸來仍是三年前端工作經(jīng)驗(yàn)。
第一篇章
Cookie 的誕生及其特點(diǎn)
眾所周知,web 服務(wù)器是無狀態(tài)的,無狀態(tài)的意思就是服務(wù)器不知道用戶上一次請(qǐng)求做了什么,各請(qǐng)求之間是相互獨(dú)立的,客戶信息僅來自于每次請(qǐng)求時(shí)攜帶的,或是服務(wù)器自身保存的且可以被所有請(qǐng)求使用的公共信息。所以為了跟蹤用戶請(qǐng)求的狀態(tài)信息,比如記錄用戶網(wǎng)上購物的購物車歷史記錄,Cookie 應(yīng)運(yùn)而生。
服務(wù)端在響應(yīng)客戶端請(qǐng)求的時(shí)候,會(huì)向客戶端推送一個(gè) Cookie,這個(gè) Cookie 記錄服務(wù)端上面的一些信息,客戶端在后續(xù)的請(qǐng)求中攜帶這個(gè) Cookie,服務(wù)端可以根據(jù)這個(gè) Cookie 判斷該請(qǐng)求的上下文關(guān)系。
Cookie 的出現(xiàn),是無狀態(tài)化向狀態(tài)化過渡的一種手段。
以登錄為例,用戶輸入賬戶名密碼,發(fā)送請(qǐng)求到服務(wù)端,服務(wù)器生成 Cookie 后發(fā)送給瀏覽器,瀏覽器把 Cookie 以 k-v 的形式保存到某個(gè)目錄下的文本文件內(nèi),下一次請(qǐng)求同一網(wǎng)站時(shí)會(huì)把該 Cookie 發(fā)送給服務(wù)器。服務(wù)器校驗(yàn)該接收的 Cookie 與服務(wù)端的 Cookie 是否一致,不一致則驗(yàn)證失敗。這是最初的設(shè)想。
在瀏覽器中存儲(chǔ)的 Cookie 在下圖所示位置
Cookie的原理決定了他有以下特點(diǎn):
1,存儲(chǔ)在客戶端,可隨意篡改,不安全
2,它的內(nèi)容會(huì)隨著 http 交互傳接,影響性能,所以 Cookie 可存儲(chǔ)的數(shù)據(jù)不能過大,最大為 4kb
3,一個(gè)瀏覽器對(duì)于一個(gè)網(wǎng)站只能存不超過 20 個(gè) Cookie,而瀏覽器一般只允許存放 300 個(gè) Cookie
4,移動(dòng)端對(duì) Cookie 支持不友好
5,一般情況下存儲(chǔ)的是純文本,對(duì)象需要序列化之后才可以存儲(chǔ),解析需要反序列化
二級(jí)域名之間的 Cookie 共享
還是以登錄 Cookie 為例,比如現(xiàn)在有兩個(gè)二級(jí)域名,http://a.xxx.com(域名 A)和http://b.xxx.com(域名 B)。那么域名 A 的登錄 Cookie 在域名 B 下可以使用嗎?
默認(rèn)情況下,域名 A 服務(wù)主機(jī)中生成的 Cookie,只有域名 A 的服務(wù)器能拿到,其他域名是拿不到這個(gè) Cookie 的,這就是僅限主機(jī)Cookie。
但是服務(wù)端可以通過顯式地聲明 Cookie 的 domian 來定義它的域,如上例子通過Set-Cookie將域名 A 的登錄 Cookie 的 domain(域)設(shè)置成http://xxx.com(他們共同的頂級(jí)域名),path 設(shè)置成’/’,Set-Cookie:name=value;domain=xxx.com;path=’/’,那么域名 B 便可以讀到。
在新的規(guī)范rfc6265 中,domain 的值會(huì)忽略任何前導(dǎo)點(diǎn),也就是**xxx.com**和**.xxx.com**都可以在子域中使用。SSO(單點(diǎn)登錄)也是依據(jù)這個(gè)原理實(shí)現(xiàn)的。
那比如現(xiàn)在又有兩個(gè)域名,a.b.e.f.com.cn (域名 1)和c.d.e.f.com.cn (域名 2),域名 2 想要讀到域名 1 的 Cookie,域名 1 可以聲明哪些 domain 呢?答案是.e.f.com.cn或.f.com.cn,瀏覽器不能接受 domian 為.com.cn 的 Cookie,因?yàn)?Cookie 域如果可以設(shè)置成后綴,那可就是峽谷大亂斗了。
那如果域名 1 設(shè)置Set-Cookie:mykey=myvalue1;domain=e.f.com.cn;path=’/’
域名 2 設(shè)置Set-Cookie:mykey=myvalue2;domain=e.f.com.cn;path=’/’
那該域下 mykey 的值會(huì)被覆蓋為 myvalue2,很好理解,同一個(gè)域下,Cookie 的 mykey 是唯一的。通常,我們要通過設(shè)置正確的 domain 和 path,減少不必要的數(shù)據(jù)傳輸,節(jié)省帶寬。
Cookie-session 模式原理
隨著交互式 Web 應(yīng)用的興起,Cookie 大小的限制以及瀏覽器對(duì)存儲(chǔ) Cookie 的數(shù)量限制,我們一定需要更強(qiáng)大的空間來儲(chǔ)存大量的用戶信息,比如我們這個(gè)網(wǎng)站是誰登錄了,誰的購物車?yán)锛尤肓松唐返鹊龋?wù)器要保存千萬甚至更多的用戶的信息,Cookie 顯然是不行的。那怎么辦呢?
試想,我們?cè)诜?wù)器端尋找一個(gè)空間存儲(chǔ)所有用戶會(huì)話的狀態(tài)信息,并給每個(gè)用戶分配不同的“身份標(biāo)識(shí)”,也就是sessionId ,再將這個(gè) sessionId 推送給瀏覽器客戶端存儲(chǔ)在 Cookie 中記錄當(dāng)前的狀態(tài),下次請(qǐng)求的時(shí)候只需要攜帶這個(gè) sessionId,服務(wù)端就可以去那個(gè)空間搜索到該標(biāo)識(shí)對(duì)應(yīng)的用戶。**這樣做既能解決 Cookie 限制問題,又不用暴露用戶信息到客戶端,大大增加了實(shí)用性和安全性。
那將用戶信息存儲(chǔ)在哪呢?能否直接存在服務(wù)器中?
如果存在服務(wù)器中,1、這對(duì)服務(wù)器說是一個(gè)巨大的開銷,嚴(yán)重的限制了服務(wù)器的擴(kuò)展能力。2、假設(shè) web 服務(wù)器做了負(fù)載均衡,用戶 user1 通過機(jī)器 A 登入該系統(tǒng),那么下一個(gè)請(qǐng)求如果被轉(zhuǎn)發(fā)到另一臺(tái)機(jī)器 B 上,機(jī)器 B 上是沒有存該用戶信息的,所以也找不到 sessionId,因此 sessionId 不應(yīng)該存儲(chǔ)在服務(wù)器上。這個(gè)時(shí)候redis/Memcached便出來解決該問題了,可以簡單的理解它們?yōu)橐粋€(gè)緩存數(shù)據(jù)庫。
當(dāng)我們把 sessionId 集中存儲(chǔ)到一個(gè)獨(dú)立的緩存服務(wù)器上,所有的機(jī)器根據(jù) sessionId 到這個(gè)緩存系統(tǒng)里去獲取用戶信息和認(rèn)證。那么問題就迎刃而解了。
Cookie-session 工作原理流程圖

根據(jù)其工作原理,你有沒有發(fā)現(xiàn)這個(gè)方式會(huì)存在一個(gè)什么樣的問題?
那就是增加了單點(diǎn)登錄失敗的可能性,如果負(fù)責(zé) session 的機(jī)器掛了, 那整個(gè)登錄也就掛了。但是一般在項(xiàng)目里,負(fù)責(zé) session 的機(jī)器也是有多臺(tái)機(jī)器的集群進(jìn)行負(fù)載均衡,增加可靠性。
思考:
假如服務(wù)器重啟的話,用戶信息會(huì)丟失嗎?
Redis 等緩存服務(wù)器也是有個(gè)集群的,假設(shè)某一臺(tái)服務(wù)重啟了,會(huì)從其他運(yùn)行的服務(wù)器中查找用戶信息,那假設(shè)真的某一次所有服務(wù)器全都崩潰了,怎么辦呢?大概的應(yīng)對(duì)策略就是,存儲(chǔ)在內(nèi)存中的用戶信息會(huì)定期刷到主機(jī)硬盤中以持久化數(shù)據(jù),即便丟失,也只會(huì)丟失重啟的那幾分鐘內(nèi)的用戶數(shù)據(jù)。
Cookie-session 局限性
1、依賴 Cookie,用戶可以在瀏覽器端禁用 Cookie
2、不支持跨端兼容 app 等
3、業(yè)務(wù)系統(tǒng)不停的請(qǐng)求緩存服務(wù)器查找用戶信息,使得內(nèi)存開銷增加,服務(wù)器壓力過大
4、服務(wù)器是有狀態(tài)的,如果是沒有緩存服務(wù)器的方式,擴(kuò)容就非常困難,需要在多臺(tái)服務(wù)器中瘋狂復(fù)制 sessionId
5、存在單點(diǎn)登錄失敗的可能性
第二篇章
SSO(單點(diǎn)登錄)三種類型
單點(diǎn)登錄(Single Sign On),簡稱為 SSO。隨著企業(yè)的發(fā)展,一個(gè)大型系統(tǒng)里可能包含 n 多子系統(tǒng),用戶在操作不同的系統(tǒng)時(shí),需要多次登錄,很麻煩,單點(diǎn)登錄就是用來解決這個(gè)問題的,在多個(gè)應(yīng)用系統(tǒng)中,只需要登錄一次,就可以訪問其他相互信任的應(yīng)用系統(tǒng)。
之前我們說過,單點(diǎn)登錄是基于 cookie 同頂域共享的,那按照不同的情況可分為以下 3 種類型。
1、同一個(gè)站點(diǎn)下;
2、系統(tǒng)在相同的頂級(jí)域名下;
3、各子系統(tǒng)屬于不同的頂級(jí)域名
一般情況下一個(gè)企業(yè)有一個(gè)頂級(jí)域名,前面講過了,同一個(gè)站點(diǎn)和相同頂級(jí)域下的單點(diǎn)登錄是利用了 Cookie 頂域共享的特性,相信大家已經(jīng)明白這個(gè)流程,不再贅述。但如果是不同域呢?不同域之間 Cookie 是不共享的,怎么辦?
CAS(中央認(rèn)證服務(wù))原理
這里我們就要說一說 CAS(中央認(rèn)證服務(wù) )流程了,這個(gè)流程是單點(diǎn)登錄的標(biāo)準(zhǔn)流程。它借助一個(gè)單獨(dú)的系統(tǒng)專門做認(rèn)證用,以下成為SSO系統(tǒng)。
它的流程其實(shí)跟 Cookie-session 模式是一樣的,單點(diǎn)登錄等于說是每個(gè)子系統(tǒng)都擁有一套完整的 Cookie-session 模式,再加上一套 Cookie-session 模式的 SSO 系統(tǒng)。
用戶訪問系統(tǒng) a,需登錄的時(shí)候跳到 SSO 系統(tǒng),在 SSO 系統(tǒng)里通過賬號(hào)密碼認(rèn)證之后,SSO 的服務(wù)器端保存 session,,并生成一個(gè) sessionId 返回給 SSO 的瀏覽器端,瀏覽器端寫入 SSO 域下的 Cookie,并生成一個(gè)生成一個(gè) ST,攜帶該 ST 傳入系統(tǒng) a,系統(tǒng) a 用這個(gè) ST 請(qǐng)求 SSO 系統(tǒng)做校驗(yàn),校驗(yàn)成功后,系統(tǒng) a 的服務(wù)器端將登錄狀態(tài)寫入 session 并種下系統(tǒng) a 域下的 Cookie。之后系統(tǒng) a 再做登錄驗(yàn)證的時(shí)候,就是同域下的認(rèn)證了。
這時(shí),用戶訪問系統(tǒng) b,當(dāng)跳到 SSO 里準(zhǔn)備登錄的時(shí)候發(fā)現(xiàn) SSO 已經(jīng)登錄了,那 SSO 生成一個(gè) ST,攜帶該 ST 傳入系統(tǒng) b,系統(tǒng) b 用這個(gè) ST 請(qǐng)求 SSO 系統(tǒng)做校驗(yàn),校驗(yàn)成功后,系統(tǒng) b 的服務(wù)器端將登錄狀態(tài)寫入 session 并設(shè)置系統(tǒng) b 域下的 Cookie。可以看得出,在這個(gè)流程里系統(tǒng) b 就不需要再走登錄了。
關(guān)于“跳到 SSO 里準(zhǔn)備登錄的時(shí)候發(fā)現(xiàn) SSO 已經(jīng)登錄了”,這個(gè)是怎么做的呢,這就涉及 Oauth2 授權(quán)機(jī)制了,在這里就不展開講,簡單提個(gè)思路,就是在系統(tǒng) b 向 SSO 系統(tǒng)跳轉(zhuǎn)的時(shí)候讓它從系統(tǒng) a 跳轉(zhuǎn),攜帶系統(tǒng) a 的會(huì)話信息跳到 SSO,再通過重定向回系統(tǒng) b。
關(guān)于 Oauth2,可移步阮一峰 的《OAuth 2.0 的四種方式》。
第三篇章
我們已經(jīng)分析過 Cookie-session 的局限性了,還有沒有更徹底的解決辦法呢?既然 SSO 認(rèn)證系統(tǒng)的存在會(huì)增加單點(diǎn)失敗的可能性,那我們是不是索性不要它?這就是去中心化的思路,即省去用來存儲(chǔ)和校驗(yàn)用戶信息的緩存服務(wù)器,以另外的方式在各自系統(tǒng)中進(jìn)行校驗(yàn)。實(shí)現(xiàn)方式簡單來說,就是把 session 的信息全部加密到 Cookie 里,發(fā)送給瀏覽器端,用 cpu 的計(jì)算能力來換取空間。
Json Web Token 模式
服務(wù)端不保存 sessionId,用戶登錄系統(tǒng)后,服務(wù)器給他下發(fā)一個(gè)令牌(token),下一次用戶再次通過 Http 請(qǐng)求訪問服務(wù)器的時(shí)候, 把這個(gè) token 通過 Http header 或者 url 帶過來進(jìn)行校驗(yàn)。為了防止別人偽造,我們可以把數(shù)據(jù)加上一個(gè)只有自己才知道的密鑰,做一個(gè)簽名,把數(shù)據(jù)和這個(gè)簽名一起作為 token 發(fā)送過去。這樣我們就不用保存 token 了,因?yàn)榘l(fā)送給用戶的令牌里,已經(jīng)包含了用戶信息。當(dāng)用戶再次請(qǐng)求過來的時(shí)候我用同樣的算法和密鑰對(duì)這個(gè) token 中的數(shù)據(jù)進(jìn)行加密,如果加密后的結(jié)果和 token 中的簽名一致,那我們就可以進(jìn)行鑒權(quán),并且也能從中取得用戶信息。
對(duì)于服務(wù)端來說,這樣只負(fù)責(zé)生成 token , 然后驗(yàn)證 token ,不再需要額外的緩存服務(wù)器存儲(chǔ)大量的 session,當(dāng)面對(duì)訪問量增加的情況,我們只需要針對(duì)訪問需求大的服務(wù)器進(jìn)行擴(kuò)容就好了,比擴(kuò)充整個(gè)用戶中心的服務(wù)器更節(jié)省。
假如有人篡改了用戶信息,但是由于密鑰是不知道的,所以 token 中的簽名和被篡改后客戶端計(jì)算出來的簽名肯定是不一致的,也會(huì)認(rèn)證失敗,所以不必?fù)?dān)心安全問題。
關(guān)于 token 的時(shí)效性,是這樣做的,首次登陸根據(jù)賬號(hào)密碼生成一個(gè) token,之后的每次請(qǐng)求,服務(wù)端更新時(shí)間戳發(fā)送一個(gè)新的 token,客戶端替換掉原來的 token。
JWT 工作原理流程圖

JWT 有什么優(yōu)劣勢(shì)
弊端
1.jwt 模式的退出登錄實(shí)際上是假的登錄失效,因?yàn)橹皇菫g覽器端清除 token 形成的假象,假如用之前的 token 只要沒過期仍然能夠登陸成功
2.安全性依賴密鑰,一旦密鑰暴露完蛋
3.加密生成的數(shù)據(jù)比較長,相對(duì)來說占用了更大的流量
優(yōu)點(diǎn)
1.不依賴 Cookie,可跨端跨程序應(yīng)用,支持移動(dòng)設(shè)備
2.相對(duì)于沒有單點(diǎn)登錄的 cookie-session 模式來說非常好擴(kuò)展
3.服務(wù)器保持了無狀態(tài)特性,不需要將用戶信息存在服務(wù)器或 Session 中
4.對(duì)于單點(diǎn)登錄需要不停的向 SSO 站點(diǎn)發(fā)送驗(yàn)證請(qǐng)求的模式節(jié)省了大量請(qǐng)求
前往微醫(yī)互聯(lián)網(wǎng)醫(yī)院在線診療平臺(tái),快速問診,3分鐘為你找到三甲醫(yī)生。
- 后臺(tái)回復(fù):typescript,獲取我寫的 typescript 系列文章,絕對(duì)精品
- 后臺(tái)回復(fù):電子書,自動(dòng)獲取我為大家整理的大量經(jīng)典電子書,省去你篩選以及下載的時(shí)間
- 后臺(tái)回復(fù):不一樣的前端,自動(dòng)獲取精選優(yōu)質(zhì)前端文章。
- 后臺(tái)回復(fù):算法,自動(dòng)獲取精選算法文章。另外也可關(guān)注我的另外一個(gè)專注算法的公眾號(hào)力扣加加。
- 后臺(tái)回復(fù):每日一薦,自動(dòng)獲取我為大家總結(jié)的每日一薦月刊,內(nèi)含精品文章,實(shí)用技巧,高效工具等等

