應(yīng)用鑒權(quán)就是當(dāng)一個(gè)用戶進(jìn)入APP時(shí)谜叹,我們需要判斷他所擁有的權(quán)利,根據(jù)權(quán)力來判斷他所能進(jìn)行的一個(gè)行為盯蝴,最為常見的就是購物網(wǎng)站的登錄以及購物支付等操作速侈。
一.鑒權(quán)的需求背景
Http的請求是無狀態(tài)的,就是說在一個(gè)Http請求中的請求方和響應(yīng)方都是無法維護(hù)狀態(tài),是一次性的废累,所以我們就不知道請求前后都發(fā)生了什么邓梅。所以我們需要標(biāo)記的功能,而瀏覽器的sessionStorage邑滨,localStorage日缨,全局變量等限制太多,就有了cookie掖看,session匣距,token等鑒權(quán)的操作。
二.cookie
cookie也是一種前端存儲(chǔ)的方式哎壳,但是他和sessionStorage毅待,localStorage等本地存儲(chǔ)的不同在于,瀏覽器向服務(wù)端發(fā)起請求的時(shí)候归榕,cooike是自動(dòng)傳過去的尸红,可以做到前端無感知,出錯(cuò)的概率更低
過程:
1.瀏覽器向服務(wù)器發(fā)起請求并傳送數(shù)據(jù)刹泄,由服務(wù)器接收數(shù)據(jù)然后設(shè)置cooike放進(jìn)響應(yīng)頭(Set-Cookie)外里,瀏覽器接收到響應(yīng)之后就會(huì)自動(dòng)存儲(chǔ)進(jìn)cookie
2.在之后的每一次請求中瀏覽器都會(huì)自動(dòng)的在請求頭之中設(shè)置cookie字段,發(fā)送給服務(wù)端
配置:
1.Domain/Path
cookie 是要限制空間范圍的特石,通過 Domain(域)/ Path(路徑)兩級(jí)盅蝗。
Domain屬性指定瀏覽器發(fā)出 HTTP 請求時(shí),哪些域名要附帶這個(gè) Cookie姆蘸。如果沒有指定該屬性风科,瀏覽器會(huì)默認(rèn)將其設(shè)為當(dāng)前 URL 的一級(jí)域名,比如 www.example.com 會(huì)設(shè)為 example.com乞旦,而且以后如果訪問example.com的任何子域名贼穆,HTTP 請求也會(huì)帶上這個(gè) Cookie。如果服務(wù)器在Set-Cookie字段指定的域名故痊,不屬于當(dāng)前域名,瀏覽器會(huì)拒絕這個(gè) Cookie玖姑。
Path屬性指定瀏覽器發(fā)出 HTTP 請求時(shí)愕秫,哪些路徑要附帶這個(gè) Cookie。只要瀏覽器發(fā)現(xiàn)焰络,Path屬性是 HTTP 請求路徑的開頭一部分戴甩,就會(huì)在頭信息里面帶上這個(gè) Cookie。比如闪彼,PATH屬性是/甜孤,那么請求/docs路徑也會(huì)包含該 Cookie协饲。當(dāng)然,前提是域名必須一致缴川。
2.Expires / Max-Age
cookie 還可以限制時(shí)間范圍茉稠,通過 Expires、Max-Age 中的一種把夸。
Expires屬性指定一個(gè)具體的到期時(shí)間而线,到了指定時(shí)間以后,瀏覽器就不再保留這個(gè) Cookie恋日。它的值是 UTC 格式膀篮。如果不設(shè)置該屬性,或者設(shè)為null岂膳,Cookie 只在當(dāng)前會(huì)話(session)有效誓竿,瀏覽器窗口一旦關(guān)閉,當(dāng)前 Session 結(jié)束闷营,該 Cookie 就會(huì)被刪除。另外知市,瀏覽器根據(jù)本地時(shí)間傻盟,決定 Cookie 是否過期,由于本地時(shí)間是不精確的嫂丙,所以沒有辦法保證 Cookie 一定會(huì)在服務(wù)器指定的時(shí)間過期娘赴。
Max-Age屬性指定從現(xiàn)在開始 Cookie 存在的秒數(shù),比如60 * 60 * 24 * 365(即一年)跟啤。過了這個(gè)時(shí)間以后诽表,瀏覽器就不再保留這個(gè) Cookie。
如果同時(shí)指定了Expires和Max-Age隅肥,那么Max-Age的值將優(yōu)先生效竿奏。
如果Set-Cookie字段沒有指定Expires或Max-Age屬性,那么這個(gè) Cookie 就是 Session Cookie腥放,即它只在本次對(duì)話存在泛啸,一旦用戶關(guān)閉瀏覽器,瀏覽器就不會(huì)再保留這個(gè) Cookie秃症。
3.Secure / HttpOnly
cookie 可以限制使用方式候址。
Secure屬性指定瀏覽器只有在加密協(xié)議 HTTPS 下,才能將這個(gè) Cookie 發(fā)送到服務(wù)器种柑。另一方面岗仑,如果當(dāng)前協(xié)議是 HTTP,瀏覽器會(huì)自動(dòng)忽略服務(wù)器發(fā)來的Secure屬性聚请。該屬性只是一個(gè)開關(guān)荠雕,不需要指定值。如果通信是 HTTPS 協(xié)議,該開關(guān)自動(dòng)打開舞虱。
HttpOnly屬性指定該 Cookie 無法通過 JavaScript 腳本拿到欢际,主要是Document.cookie屬性、XMLHttpRequest對(duì)象和 Request API 都拿不到該屬性矾兜。這樣就防止了該 Cookie 被腳本讀到损趋,只有瀏覽器發(fā)出 HTTP 請求時(shí),才會(huì)帶上該 Cookie椅寺。
Http頭對(duì)cookie的讀寫:
響應(yīng)會(huì)攜帶一個(gè)Set-Cookie頭浑槽,一個(gè)Set-Cookie只能設(shè)置一條cookie,格式為cookie鍵值+配置鍵值返帕,如果想要一次設(shè)置多個(gè)cookie桐玻,我們可以多寫幾個(gè)Set-Cookie在頭里面。
Set-Cookie: username=jimu; domain=jimu.com; path=/blog; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
而當(dāng)瀏覽器請求服務(wù)器的時(shí)候荆萤,就不再需要發(fā)送配置內(nèi)容了镊靴,只需要發(fā)送鍵值對(duì)就可以
Cookie: username=jimu; height=180; weight=80
前端對(duì)cookie的讀寫操作:
如果服務(wù)端設(shè)置的cookie并沒有設(shè)置httpOnly,那么我們就可以調(diào)用document.cookie來對(duì)cookie進(jìn)行讀寫操作,但一次調(diào)用document.cookie就只能操作一個(gè)cookie
三.session
在上面我們介紹了cookie链韭,可以了解到cookie其實(shí)是瀏覽器存儲(chǔ)的一種實(shí)現(xiàn)偏竟,可以看作應(yīng)用鑒權(quán)的基石。但是它只是一個(gè)存儲(chǔ)信息的工具敞峭,我們還需要判斷其中的信息是不是安全的操作者踊谋,這時(shí)候我們就需要session了。
典型的session登錄流程:
1.瀏覽器登錄發(fā)送賬號(hào)密碼旋讹,服務(wù)端查用戶庫殖蚕,校驗(yàn)用戶
2.服務(wù)端把用戶登錄狀態(tài)存為 Session,生成一個(gè) sessionId
3.通過登錄接口返回沉迹,把 sessionId set 到 cookie 上
此后瀏覽器再請求業(yè)務(wù)接口睦疫,sessionId 隨 cookie 帶上
4.服務(wù)端查 sessionId 校驗(yàn) session
5.成功后正常做業(yè)務(wù)處理,返回結(jié)果
session的存儲(chǔ)與過期銷毀:
由于session是用來驗(yàn)證的鞭呕,所以服務(wù)端僅僅只是給瀏覽器的cookie中添加一個(gè)sessionid笼痛,所以也需要自己保存一下,存儲(chǔ)的方式:
1.Redis(推薦):內(nèi)存型數(shù)據(jù)庫琅拌,redis中文官方網(wǎng)站缨伊。以 key-value 的形式存,正合 sessionId-sessionData 的場景进宝;且訪問快刻坊。
2.內(nèi)存:直接放到變量里。一旦服務(wù)重啟就沒了
3.數(shù)據(jù)庫:普通數(shù)據(jù)庫党晋。性能不高谭胚。
而session也可以手動(dòng)設(shè)置過期時(shí)間徐块,一到過期時(shí)間就直接清空存儲(chǔ)的內(nèi)存就好了
session的分布式問題:
由于服務(wù)端是集群,而用戶請求過來會(huì)走一次負(fù)載均衡灾而,不一定會(huì)打到哪臺(tái)機(jī)器上胡控。一旦用戶后續(xù)接口請求到的機(jī)器和他登錄請求的機(jī)器不一樣,或者登錄請求的機(jī)器出問題了旁趟,那session就會(huì)失效昼激。
常見的解決方式:
1.把session集中存儲(chǔ)到獨(dú)立的redis或者普通數(shù)據(jù)庫中,就可以把session存儲(chǔ)到一個(gè)庫里(存儲(chǔ)角度)
2.讓相同 IP 的請求在負(fù)載均衡時(shí)都打到同一臺(tái)機(jī)器上锡搜。以 nginx 為例橙困,可以配置 ip_hash 來實(shí)現(xiàn)。(分布角度)
四.token
1.用戶登錄耕餐,服務(wù)端校驗(yàn)賬號(hào)密碼凡傅,獲得用戶信息
2.把用戶信息、token 配置編碼成 token肠缔,通過 cookie set 到瀏覽器
3.此后用戶請求業(yè)務(wù)接口夏跷,通過 cookie 攜帶 token
4.接口校驗(yàn) token 有效性,進(jìn)行正常業(yè)務(wù)接口處理
目前主流的token存儲(chǔ)還是在cookie中進(jìn)行明未,但是為了防止偽造token造成的安全問題槽华,我們還需要一些加密算法來生成簽名,來保證數(shù)據(jù)安全性
JWT
由于以上的方法增加了cookie的數(shù)量亚隅,所以JSON web Token得以被使用硼莽。
refresh token
token庶溶,作為權(quán)限守護(hù)者煮纵,最重要的就是「安全」。
業(yè)務(wù)接口用來鑒權(quán)的 token偏螺,我們稱之為 access token行疏。越是權(quán)限敏感的業(yè)務(wù),我們越希望 access token 有效期足夠短套像,以避免被盜用酿联。但過短的有效期會(huì)造成 access token 經(jīng)常過期,過期后怎么辦呢夺巩?
一種辦法是贞让,讓用戶重新登錄獲取新 token,顯然不夠友好柳譬,要知道有的 access token 過期時(shí)間可能只有幾分鐘喳张。
另外一種辦法是,再來一個(gè) token美澳,一個(gè)專門生成 access token 的 token销部,我們稱為 refresh token摸航。
1.access token 用來訪問業(yè)務(wù)接口,由于有效期足夠短舅桩,盜用風(fēng)險(xiǎn)小酱虎,也可以使請求方式更寬松靈活
2.refresh token 用來獲取 access token,有效期可以長一些擂涛,通過獨(dú)立服務(wù)和嚴(yán)格的請求方式增加安全性读串;由于不常驗(yàn)證,也可以如前面的 session 一樣處理
如果refresh token過期了歼指,就只能重新登陸了爹土。
五.session與token對(duì)比
客戶端存cookie與存放于其他地方
1.出了瀏覽器環(huán)境之外就沒有cookie了;
2.cookie是瀏覽器在域下自動(dòng)攜帶的踩身。很容易引發(fā)CSFR攻擊
存放在別的地方可以解決部分問題
服務(wù)端存儲(chǔ)數(shù)據(jù)于不存
1.存數(shù)據(jù)的話可以縮短認(rèn)證字符串的長度胀茵,減小請求體積
2.不存數(shù)據(jù)就不會(huì)出現(xiàn)分布式處理的問題,降低硬件成本挟阻,避免查庫帶來的驗(yàn)證延遲
六.單點(diǎn)登錄
前面我們已經(jīng)知道了琼娘,在同域下的客戶端/服務(wù)端認(rèn)證系統(tǒng)中,通過客戶端攜帶憑證附鸽,維持一段時(shí)間內(nèi)的登錄狀態(tài)脱拼。
但當(dāng)我們業(yè)務(wù)線越來越多,就會(huì)有更多業(yè)務(wù)系統(tǒng)分散到不同域名下坷备,就需要「一次登錄熄浓,全線通用」的能力,叫做「單點(diǎn)登錄」省撑。
在主域名相同的情況下赌蔑,就可以直接把cookie設(shè)置為主域名就可以實(shí)現(xiàn)了。
如果主域名不同竟秫,我們就需要獨(dú)立的認(rèn)證服務(wù)娃惯,稱為SSO。
1.用戶進(jìn)入 A 系統(tǒng)肥败,沒有登錄憑證(ticket)趾浅,A 系統(tǒng)給他跳到 SSO
2.SSO 沒登錄過,也就沒有 sso 系統(tǒng)下沒有憑證(注意這個(gè)和前面 A ticket 是兩回事)馒稍,輸入賬號(hào)密碼登錄
3.SSO 賬號(hào)密碼驗(yàn)證成功皿哨,通過接口返回做兩件事:一是種下 sso 系統(tǒng)下憑證(記錄用戶在 SSO 登錄狀態(tài));二是下發(fā)一個(gè) ticket
4.客戶端拿到 ticket纽谒,保存起來证膨,帶著請求系統(tǒng) A 接口
5.系統(tǒng) A 校驗(yàn) ticket,成功后正常處理業(yè)務(wù)請求
6.此時(shí)用戶第一次進(jìn)入系統(tǒng) B佛舱,沒有登錄憑證(ticket)椎例,B 系統(tǒng)給他跳到 SSO
7.SSO 登錄過挨决,系統(tǒng)下有憑證,不用再次登錄订歪,只需要下發(fā) ticket
8.客戶端拿到 ticket脖祈,保存起來,帶著請求系統(tǒng) B 接口
如果是在瀏覽器之下實(shí)現(xiàn)刷晋,我們需要考慮其他的東西
對(duì)瀏覽器來說盖高,SSO 域下返回的數(shù)據(jù)要怎么存,才能在訪問 A 的時(shí)候帶上眼虱?瀏覽器對(duì)跨域有嚴(yán)格限制喻奥,cookie、localStorage 等方式都是有域限制的捏悬。
這就需要也只能由 A 提供 A 域下存儲(chǔ)憑證的能力撞蚕。一般我們是這么做的:
圖中我們通過顏色把瀏覽器當(dāng)前所處的域名標(biāo)記出來。注意圖中灰底文字說明部分的變化过牙。
1.在 SSO 域下甥厦,SSO 不是通過接口把 ticket 直接返回,而是通過一個(gè)帶 code 的 URL 重定向到系統(tǒng) A 的接口上寇钉,這個(gè)接口通常在 A 向 SSO 注冊時(shí)約定
2.瀏覽器被重定向到 A 域下刀疙,帶著 code 訪問了 A 的 callback 接口,callback 接口通過 code 換取 ticket
3.這個(gè) code 不同于 ticket扫倡,code 是一次性的谦秧,暴露在 URL 中,只為了傳一下?lián)Q ticket撵溃,換完就失效
4.callback 接口拿到 ticket 后疚鲤,在自己的域下 set cookie 成功
5.在后續(xù)請求中,只需要把 cookie 中的 ticket 解析出來征懈,去 SSO 驗(yàn)證就好
6.訪問 B 系統(tǒng)也是一樣
七.總結(jié)
1.HTTP 是無狀態(tài)的石咬,為了維持前后請求揩悄,需要前端存儲(chǔ)標(biāo)記
2.cookie 是一種完善的標(biāo)記方式卖哎,通過 HTTP 頭或 js 操作,有對(duì)應(yīng)的安全策略删性,是大多數(shù)狀態(tài)管理方案的基石
3.session 是一種狀態(tài)管理方案亏娜,前端通過 cookie 存儲(chǔ) id,后端存儲(chǔ)數(shù)據(jù)蹬挺,但后端要處理分布式問題
4.token 是另一種狀態(tài)管理方案维贺,相比于 session 不需要后端存儲(chǔ),數(shù)據(jù)全部存在前端巴帮,解放后端溯泣,釋放靈活性
5.token 的編碼技術(shù)虐秋,通常基于 base64垃沦,或增加加密算法防篡改客给,jwt 是一種成熟的編碼方案
6.在復(fù)雜系統(tǒng)中,token 可通過 service token肢簿、refresh token 的分權(quán)靶剑,同時(shí)滿足安全性和用戶體驗(yàn)
7.session 和 token 的對(duì)比就是「用不用cookie」和「后端存不存」的對(duì)比
8.單點(diǎn)登錄要求不同域下的系統(tǒng)「一次登錄,全線通用」池充,通常由獨(dú)立的 SSO 系統(tǒng)記錄登錄狀態(tài)桩引、下發(fā) ticket,各業(yè)務(wù)系統(tǒng)配合存儲(chǔ)和認(rèn)證 ticket
閱讀知乎文章:鑒權(quán)必須了解的5個(gè)兄弟:cookie收夸、session坑匠、token、jwt卧惜、單點(diǎn)登錄 - 知乎 (zhihu.com)