安全是計(jì)算機(jī)科學(xué)永遠(yuǎn)無(wú)法忽視的話題塘安。隨著互聯(lián)網(wǎng)的發(fā)展卧斟,安全問(wèn)題越來(lái)越突出殴边,也越來(lái)越重要:它是一個(gè)程序可用性憎茂、健壯性的基礎(chǔ)。這個(gè)話題可大可小锤岸,大到系統(tǒng)的設(shè)計(jì)竖幔,小到一行代碼的寫法,都可能影響系統(tǒng)的安全是偷。
毫不例外拳氢,安全與前端開發(fā)的結(jié)合也持續(xù)走熱。不管是經(jīng)驗(yàn)豐富的程序員蛋铆,還是尚在打基礎(chǔ)的學(xué)生馋评,也許都對(duì) HTTPS、XSS刺啦、CSRF 等前端相關(guān)的安全問(wèn)題不陌生留特。然而,這其中每一個(gè)主題都可以非常深入玛瘸,都能系統(tǒng)地做一節(jié)課磕秤。但是,我認(rèn)為面面俱到捧韵、走馬觀花地梳理這些內(nèi)容市咆,講解這些概念價(jià)值不大。畢竟再来,這方面知識(shí)都已經(jīng)比較成熟蒙兰,社區(qū)上資料很多。
本講我想從一個(gè)大部分產(chǎn)品都要涉及的登錄鑒權(quán)入手芒篷,結(jié)合單頁(yè)面應(yīng)用搜变,從這個(gè)角度,管中窺豹针炉,盡可能多地涉及一些常見的安全知識(shí)挠他,幫助大家了解前端安全。
接下來(lái)篡帕,讓我們從應(yīng)用場(chǎng)景入手殖侵,從前后端交互切入,以單頁(yè)面應(yīng)用為基礎(chǔ)镰烧,呈現(xiàn)「鑒權(quán)」這個(gè)安全領(lǐng)域重要話題的全貌拢军,并盡力覆蓋到 XSS 和 CSRF 等攻擊手段以及最佳實(shí)踐。
關(guān)于這個(gè)主題的知識(shí)點(diǎn)如下:
單頁(yè)應(yīng)用鑒權(quán)簡(jiǎn)介
首先怔鳖,我們要分清單頁(yè)應(yīng)用鑒權(quán)與傳統(tǒng)鑒權(quán)方式有所不同:
單頁(yè)應(yīng)用采用前后端分離的設(shè)計(jì)方式茉唉,路由由前端管理,前后端遵循一定規(guī)范(如 REST、GraphQL)度陆,通過(guò) AJAX 進(jìn)行通信艾凯。在這種情況下,用戶對(duì)頁(yè)面請(qǐng)求時(shí)懂傀,后端經(jīng)常無(wú)法獲取用戶身份信息趾诗,更無(wú)法確定返回的數(shù)據(jù)。
同時(shí)一次鑒權(quán)完畢后鸿竖,如何在單頁(yè)應(yīng)用的體驗(yàn)當(dāng)中,保持這個(gè)鑒權(quán)狀態(tài)也值得思考铸敏。一般來(lái)說(shuō)缚忧,單頁(yè)應(yīng)用鑒權(quán)采用下面的步驟實(shí)現(xiàn)。
- Step 1:前端根據(jù)用戶交互杈笔,發(fā)送數(shù)據(jù)請(qǐng)求之前闪水,需要準(zhǔn)備用戶信息,同數(shù)據(jù)請(qǐng)求一起發(fā)給后端處理蒙具。
- Step 2-1:后端按照約定好的規(guī)則球榆,根據(jù)請(qǐng)求中帶有的用戶身份信息,進(jìn)行驗(yàn)證禁筏。如果驗(yàn)證不通過(guò)持钉,返回 403 或者 401 相關(guān)狀態(tài)碼或其他狀態(tài)篱昔,以表示鑒權(quán)失敗每强。
- Step 2-2:如果鑒權(quán)成功,后端返回相關(guān)數(shù)據(jù)州刽。
- Step 3:前端根據(jù)數(shù)據(jù)渲染視圖空执。
基本結(jié)構(gòu)非常簡(jiǎn)單清晰:
在這個(gè)結(jié)構(gòu)背后,隱藏的技術(shù)方案和安全細(xì)節(jié)非常值得我們思考穗椅,請(qǐng)繼續(xù)閱讀辨绊,我們將剖析幾個(gè)重要概念和安全實(shí)踐。
HTTPS
鑒權(quán)過(guò)程中匹表,如果使用 HTTP 協(xié)議來(lái)傳輸敏感數(shù)據(jù)(用戶昵稱门坷、用戶密碼、token……)袍镀,那么很容易被中間人攔截獲取“莺祝現(xiàn)代通信中,我們都使用 HTTPS 協(xié)議來(lái)對(duì)傳輸內(nèi)容進(jìn)行加密流椒。關(guān)于 HTTPS 的應(yīng)用及其原理敏簿,又是一個(gè)超級(jí)話題。這里由于內(nèi)容的限制,不過(guò)多展開惯裕,給大家分享一下我收藏的關(guān)于 HTTPS 好的文章:
- https 連接的前幾毫秒發(fā)生了什么
- 完全圖解 HTTPS
- 更安全的 Web 通信 HTTPS
- 圖解基于 HTTPS 的 DNS
- 看圖學(xué) HTTPS
- http 與 https 的區(qū)別我真的知道嗎
- 深入揭秘 HTTPS 安全問(wèn)題&連接建立全過(guò)程
- HTTPS 系列干貨(一):HTTPS 原理詳解
- HTTPS 為什么更安全纯趋,先看這些
不要使用 URL query 傳遞敏感數(shù)據(jù)
URL query 會(huì)通過(guò)服務(wù)端日志、瀏覽器日志矩动、瀏覽器歷史記錄查到屋彪。不要使用 URL query 傳遞敏感數(shù)據(jù),這當(dāng)然是最基本的準(zhǔn)則之一握玛。如果敏感數(shù)據(jù)在 URL query 中够傍,這就給了惡意用戶輕松獲取數(shù)據(jù)的機(jī)會(huì)。同時(shí)挠铲,URL query 的長(zhǎng)度也有限制冕屯,這也是其傳遞數(shù)據(jù)的弊端之一。
防止暴力攻擊的手段
攻擊者可以通過(guò)暴力手段拂苹,嘗試攻破用戶的密碼等信息安聘。因此后端服務(wù)要時(shí)刻注意加入頻率限制,限制一個(gè)用戶短時(shí)間嘗試密碼的次數(shù)瓢棒;也可以限制可疑用戶(比如觸發(fā)了過(guò)多服務(wù)端錯(cuò)誤用戶)的訪問(wèn)浴韭。另外,需要注意的是不要給任何人暴露服務(wù)端的技術(shù)細(xì)節(jié)信息脯宿,比如要記得關(guān)閉 X-Powered-By(服務(wù)器響應(yīng)頭隱藏)念颈;Node 端在使用 express.js 的情況下,強(qiáng)烈建議使用 Helmetjs连霉。
Helmet 幫助 Node.js 開發(fā)者通過(guò)設(shè)置合理的 HTTP header舍肠,預(yù)防一些常見的 Web 漏洞,比如上面提到的關(guān)閉 X-Powered-By窘面。實(shí)際上它就是一組靈活的中間件函數(shù)翠语,增強(qiáng)以下 HTTP header 的安全性:
Content-Security-Policy 響應(yīng)頭,它可以設(shè)置應(yīng)用是否可以引用某些來(lái)源內(nèi)容财边,進(jìn)而防止 XSS
關(guān)閉 X-Powered-By 響應(yīng)頭肌括,以避免暴露服務(wù)端信息
增加 Public Key Pinning 響應(yīng)頭,預(yù)防中間人偽造證書
設(shè)置 Strict-Transport-Security 響應(yīng)頭酣难,這樣瀏覽器只能通過(guò) HTTPS 訪問(wèn)當(dāng)前資源
為 IE8+ 設(shè)置 X-Download-Options 響應(yīng)頭谍夭,目前只有 IE8+ 支持這個(gè) header,用來(lái)預(yù)防下載內(nèi)容的安全隱患
設(shè)置 Cache-Control 和 Pragma header 以關(guān)閉瀏覽器端緩存
設(shè)置 X-Content-Type-Options 響應(yīng)頭憨募,以禁用瀏覽器內(nèi)容嗅探
設(shè)置 X-Frame-Options 響應(yīng)頭紧索,以預(yù)防 clickjacking,這個(gè)響應(yīng)頭給瀏覽器指示是否允許在 或者 標(biāo)簽中渲染某個(gè)頁(yè)面
設(shè)置 X-XSS-Protection 響應(yīng)頭菜谣,當(dāng)檢測(cè)到跨站腳本攻擊(XSS)時(shí)珠漂,瀏覽器停止加載頁(yè)面
它的使用非常簡(jiǎn)單:
const express = require('express')
const helmet = require('helmet')
const app = express()
app.use(helmet())
其源碼是典型的 express 中間件寫法晚缩,它依次加載相關(guān)中間件集。比如它將引用 X-Powered-By 中間件媳危,這個(gè)中間件的源碼非常簡(jiǎn)單:
module.exports = function hidePoweredBy (options) {
var setTo = (options || {}).setTo
if (setTo) {
return function hidePoweredBy (req, res, next) {
res.setHeader('X-Powered-By', setTo)
next()
}
} else {
return function hidePoweredBy (req, res, next) {
res.removeHeader('X-Powered-By')
next()
}
}
}
通過(guò) setHeader 和 removeHeader 方法荞彼,完成對(duì) X-Powered-By 響應(yīng)頭的添加和刪除。
升級(jí)依賴保證安全
現(xiàn)如今我們的應(yīng)用待笑,大部分腳本都來(lái)自第三方依賴鸣皂,第三方庫(kù)出現(xiàn)安全隱患的新聞已經(jīng)屢見不鮮。除了從源頭把控依賴的引入外暮蹂,適時(shí)合理地更新 npm 包寞缝,是值得倡導(dǎo)的做法,npm 便在 6.0 后有相關(guān)命令如下:
# npm 6.0 新增仰泻,掃描所有依賴荆陆,列出依賴中有安全隱患的包
npm audit
# npm 6.0 新增,掃描所有依賴我纪,并把不安全的依賴包升級(jí)到可兼容的版本
npm audit fix
單頁(yè)應(yīng)用鑒權(quán)實(shí)戰(zhàn)
言歸正傳慎宾,我們來(lái)看一下實(shí)現(xiàn)單頁(yè)應(yīng)用鑒權(quán)的兩種主要手段:
- JWT
- Authentication cookie
這兩種方式不盡相同丐吓,我們將逐一分析浅悉,并嘗試合并這兩種方案的優(yōu)點(diǎn),將它們結(jié)合為第三種方式券犁。
采用 JWT 實(shí)現(xiàn)鑒權(quán)
在鑒權(quán)過(guò)程中术健,為了驗(yàn)證用戶的身份,需要瀏覽器向服務(wù)器端提供一個(gè)驗(yàn)證信息粘衬,我們稱為 token荞估。這個(gè) token 通常由 JSON 數(shù)據(jù)格式組成,通過(guò) hash 散列算法生成一個(gè)字符串稚新,稱為 JSON Web Token(JSON 表示令牌的原始類型為 JSON 格式勘伺,Web 表示在互聯(lián)網(wǎng)中進(jìn)行傳播,Token 表示令牌褂删,簡(jiǎn)稱 JWT)飞醉。任何 token 持有者都可以無(wú)差別地用它來(lái)訪問(wèn)相關(guān)的資源。
我們可以在 HTTP Authorization header 中找到 token屯阀,其實(shí)就是一個(gè)字符串值缅帘。這個(gè)字符串用來(lái)表示用戶的身份信息,進(jìn)行身份認(rèn)證或者從服務(wù)器獲取合法資源难衰。當(dāng)然這個(gè) token 往往是被加密的钦无。那么這個(gè) token 具體是如何生成的呢?
我們先從 JWT 說(shuō)起盖袭,一個(gè) JWT 包含以下 3 個(gè)部分:
- header(消息頭)
- payload(消息體失暂,儲(chǔ)存用戶 id彼宠、用戶角色等) + 過(guò)期時(shí)間(可選)
- signature(簽名)
我們說(shuō)過(guò),JWT 就是 JSON 格式的數(shù)據(jù)趣席,JWT 的前兩個(gè)部分就是 JSON 數(shù)據(jù)兵志,第三部分 signature 是基于前兩部分 header 和 payload 生成的簽名。前兩部分分別通過(guò) Base64URL 算法生成兩組字符串宣肚,再和 signature 結(jié)合想罕,三部分通過(guò) . 號(hào)分割,就是最終的 token霉涨。
更多這方面的信息按价,大家可以參考:
正常來(lái)講,當(dāng)客戶端在提交用戶名/密碼(或者其他方式)通過(guò)認(rèn)證后笙瑟,會(huì)獲得 JWT 的 token楼镐,接著通過(guò) JavaScript 腳本,對(duì)于所有數(shù)據(jù)請(qǐng)求都在其 HTTP header 中加上這個(gè) JWT 的 token往枷。服務(wù)端接到請(qǐng)求之后框产,驗(yàn)證 token 的 signature 是否等同于 payload,進(jìn)而得知 payload 字段是否被中間人更改错洁。
細(xì)心的讀者可能會(huì)發(fā)現(xiàn)秉宿,我們提到「通過(guò) JavaScript 腳本,對(duì)于所有數(shù)據(jù)請(qǐng)求屯碴,都在 HTTP header 中加上這個(gè) token」描睦。這就涉及客戶端如何存儲(chǔ)和維護(hù) JWT 的問(wèn)題了。
存儲(chǔ) JWT导而,我們可以考慮:
- 內(nèi)存存儲(chǔ)
- local/session cookie
- local/session storage……
這幾種方式忱叭。我并不建議開發(fā)者將 token 存儲(chǔ)在 local storage 當(dāng)中,因?yàn)椋?/p>
- 當(dāng)用戶關(guān)掉瀏覽器后今艺,JWT 仍然會(huì)被存儲(chǔ)在 local storage 中韵丑,即便 JWT 過(guò)期,可能一直被存儲(chǔ)(除非手動(dòng)更新或清理)
- 任何 JavaScript 都能輕而易舉地獲得 local storage 的內(nèi)容
- 無(wú)法被 web worker 使用
但在實(shí)際項(xiàng)目中虚缎,筆者也在 local storage 中存儲(chǔ)過(guò) JWT撵彻,這需要我們分清利弊,結(jié)合實(shí)際場(chǎng)景選擇方案遥巴。如果吃透概念千康,就能減少 bug 的出現(xiàn),具體存儲(chǔ)方案可以靈活一些铲掐。
更好的選擇之一是將 JWT 存儲(chǔ)在 session cookie 中拾弃,auth0 有一篇很好的文章,感興趣的讀者可以參考:Where to Store Tokens摆霉。
JWT 隱患
JWT 實(shí)現(xiàn)鑒權(quán)也存在的隱患豪椿,上面我們也簡(jiǎn)要提到了奔坟,隱患主要來(lái)自 XSS。攻擊者可以主動(dòng)注入惡意腳本或者使用用戶輸入搭盾,通過(guò) JavaScript 代碼來(lái)偷取 token咳秉,接下來(lái)便能通過(guò) token 冒充受害用戶。
比如鸯隅,一個(gè)博客留言系統(tǒng)澜建,用戶可以在其留言內(nèi)容中加入以下腳本:
<img src="x" onerror="javascript:alert('XSS')" style="height: 1px; width: 100%; />
一般的防御手段是采用 HTML 轉(zhuǎn)義來(lái)控制過(guò)濾用戶輸入(為了防止 XSS 攻擊,常常需要將用戶輸入的特殊字符進(jìn)行轉(zhuǎn)義)蝌以。
采用 Authentication cookie 實(shí)現(xiàn)鑒權(quán)
cookie 是含有有效期和相關(guān) domain炕舵,存儲(chǔ)在瀏覽器中的鍵值對(duì)組合,可以由 JavaScript 創(chuàng)建:
document.cookie = 『my_cookie_name=my_cookie_value』
也可以由服務(wù)端通過(guò) response header 創(chuàng)建:
Set-Cookie: my_cookie_name=my_cookie_value
瀏覽器會(huì)自動(dòng)在每個(gè)請(qǐng)求當(dāng)中加入相關(guān) domain 下的 cookie:
GET https://www.example.com/api/users Cookie: my_cookie_name=my_cookie_value
cookie 一般分為兩種(出處):
Session cookie跟畅,這種 cookie 會(huì)隨著用戶關(guān)閉瀏覽器而被清除咽筋,不會(huì)被標(biāo)記任何過(guò)期時(shí)間 Expires 或者最大時(shí)限 Max-Age。
Permanent cookie徊件,與 session cookie 相反奸攻,會(huì)在用戶關(guān)閉瀏覽器之后被瀏覽器持久化存儲(chǔ)。
同時(shí)虱痕,服務(wù)端可以對(duì) cookie 進(jìn)行一些關(guān)鍵配置睹耐,以保障 cookie 的使用安全,諸如:
- HttpOnly cookie:瀏覽器端 JavaScript 沒(méi)有讀 cookie 權(quán)限皆疹。
- Secure cookie:傳輸鏈路只有在特定安全通道(通常指 HTTPS)疏橄,請(qǐng)求才會(huì)自動(dòng)加入相關(guān) cookie占拍。
- SameSite cookie:在跨域情況下略就,相關(guān) cookie 無(wú)法被請(qǐng)求攜帶,這里主要是為了防止 CSRF 攻擊晃酒。
一個(gè)經(jīng)典場(chǎng)景就是使用 cookie 存儲(chǔ)一個(gè) session ID(session ID 由服務(wù)端管理表牢,進(jìn)行創(chuàng)建和計(jì)時(shí),以便在必要的時(shí)候清除)贝次。通過(guò)驗(yàn)證 cookie 和 session ID崔兴,服務(wù)端便能標(biāo)記一個(gè)用戶的訪問(wèn)信息。這種情況就是我們說(shuō)的 stateful蛔翅,而本節(jié)課的主角 JWT 是 stateless 的敲茄,因?yàn)樗恍枰?wù)端維護(hù) session ID,是無(wú)狀態(tài)的山析,更加利于橫向擴(kuò)展堰燎。
Authentication cookie 隱患
采用 Authentication cookie 實(shí)現(xiàn)單頁(yè)應(yīng)用鑒權(quán)的安全隱患主要有兩種:
XSS 如果沒(méi)有使用 httpOnly 選項(xiàng),那么攻擊者可能會(huì)通過(guò)注入惡意腳本笋轨,任意讀取用戶 cookie秆剪。而 cookie 直接存儲(chǔ)了用戶的身份認(rèn)證信息赊淑,這當(dāng)然是非常可怕的仅讽。
CSRF) 是常見的針對(duì) cookie 展開進(jìn)攻的手段陶缺。我們知道跨域訪問(wèn)技術(shù)(CORS,跨域資源共享)的同源策略能保證不同源的客戶端腳本在沒(méi)有明確授權(quán)的情況下洁灵,無(wú)法讀寫對(duì)方資源饱岸。同源策略只是針對(duì)瀏覽器側(cè)的編程腳本語(yǔ)言,如果我們對(duì)另一個(gè)惡意服務(wù)器發(fā)送 AJAX 請(qǐng)求徽千,同源策略會(huì)有所限制伶贰,但是如果請(qǐng)求直接通過(guò) HTML form 發(fā)送,那么同源策略毫無(wú)辦法罐栈。
另一個(gè)利用 CSRF 實(shí)施攻擊的場(chǎng)景為:假如受害者在網(wǎng)頁(yè)中登錄了 Facebook黍衙,同時(shí)又打開了 bad.com,bad.com 屬于攻擊者的網(wǎng)站荠诬,這個(gè)網(wǎng)站中有這樣的代碼:
總結(jié) 為了防御 XSS 攻擊琅翻,需要開發(fā)者設(shè)置 httpOnly
選項(xiàng);為了防御 XSRF柑贞,需要開發(fā)者設(shè)置 SameSite
選項(xiàng)方椎。需要注意,并不是所有瀏覽器都支持 SameSite钧嘶。
此外棠众,一些其他防御手段有:
Short session timeout:設(shè)置 session 過(guò)期時(shí)間,比如銀行網(wǎng)站往往需要每 10 分鐘或者更短時(shí)間就重新登錄有决。
關(guān)鍵操作需要用戶重新進(jìn)行鑒權(quán)認(rèn)證闸拿。
Double submitted cookie:當(dāng)用戶瀏覽一個(gè)站點(diǎn)時(shí),服務(wù)端生成一個(gè)偽隨機(jī)數(shù) pseudorandom value书幕,并將其設(shè)置為 cookie新荤,且不設(shè)置 httpOnly 標(biāo)識(shí)。這樣 JavaScript 就能夠訪問(wèn)這個(gè) pseudorandom value台汇,并要求在提交每個(gè)表單時(shí)苛骨,一并將這個(gè) pseudorandom value 作為 form value 提交上來(lái),同時(shí)在 cookie 中也要提交 value苟呐。服務(wù)端便可以對(duì)比 form value 中的 pseudorandom value 和 cookie value 是否一致痒芝,以此來(lái)認(rèn)證用戶的安全身份。
Double submitted cookie 之所以能有效防范攻擊牵素,是因?yàn)橥床呗灾率构粽邿o(wú)法讀取來(lái)自攻擊目標(biāo)服務(wù)端的 cookie 值严衬,更無(wú)法修改攻擊網(wǎng)站的 cookie value。即便攻擊者可以從 form 中提交任何 form value两波,但是無(wú)法通過(guò)服務(wù)端對(duì) form value 中的 pseudorandom value 和 cookie value 的一致性進(jìn)行驗(yàn)證瞳步。
混合 JWT 和 cookie 進(jìn)行鑒權(quán)
設(shè)想我們要實(shí)現(xiàn)這樣一個(gè)鑒權(quán)系統(tǒng):
- 盡可能抵御 XSS 和 CSRF
- 做到 stateless
考慮到安全性能闷哆,JWT 方案的主要問(wèn)題在于攻擊者存在直接讀取 JWT 信息的可能。如果我們將 JWT 和 cookie 方案結(jié)合呢单起?即將 JWT 部分敏感信息放入 cookie 當(dāng)中抱怔,這樣一來(lái),便可以結(jié)合前文兩種方式的優(yōu)點(diǎn)嘀倒。
如圖屈留,我們?cè)倏偨Y(jié)一下存在的三種交互可能。第一種是經(jīng)典 JWT 方式:
這種情況下测蘑,前后端使用 JWT 進(jìn)行鑒權(quán)交互灌危,前端通過(guò) JavaScript 操作 JWT 信息完成請(qǐng)求準(zhǔn)備。
第二種方式碳胳,將 JWT 信息在 session cookie 中維護(hù):
在這種情況下勇蝙,JWT 信息全部存儲(chǔ)在 cookie 中, 并設(shè)置 cookie 的 httpOnly挨约、SameSite味混、Secure 屬性,前端無(wú)法讀取 JWT 信息诫惭,但每次請(qǐng)求都會(huì)由瀏覽器帶上必要的 JWT 數(shù)據(jù)(作為 cookie)翁锡。同時(shí),由于采用 session cookie夕土,也不存在 JWT 信息過(guò)期的情況馆衔,用戶關(guān)閉頁(yè)面之后不會(huì)將 JWT 信息持久化存儲(chǔ),下次再打開頁(yè)面時(shí)怨绣,會(huì)重新進(jìn)行鑒權(quán)流程角溃。
第一種方式有一定的安全隱患;第二種方式我們將 JWT 所有信息存儲(chǔ)在 session cookie 當(dāng)中梨熙,優(yōu)點(diǎn)明顯开镣,但是無(wú)法做到持久化存儲(chǔ)刀诬,在某種程度上也會(huì)帶來(lái)不便咽扇。那么我們權(quán)衡之后進(jìn)行了變通,結(jié)合前面兩種方式產(chǎn)生了第三種方式:
這樣陕壹,JWT 的 signature 部分維護(hù)在設(shè)置了 httpOnly 的 cookie 中质欲,這意味著 JavaScript 無(wú)法讀取完整的 JWT 信息。同時(shí)糠馆,cookie 會(huì)在每次請(qǐng)求中被攜帶嘶伟, 并由服務(wù)端返回后在瀏覽器中進(jìn)行存儲(chǔ),這樣 JWT 信息在每次請(qǐng)求時(shí)都可以被更新又碌,JWT 過(guò)期時(shí)間也會(huì)被自動(dòng)加入九昧。
這篇文章:Getting Token Authentication Right in a Stateless Single Page Application 就很好地對(duì)上述方式進(jìn)行了總結(jié)绊袋。
為了實(shí)現(xiàn)最大限度的安全保障,我們也可以考慮結(jié)合前文介紹的 Double submitted cookie 以及「關(guān)鍵操作需要用戶重新進(jìn)行鑒權(quán)認(rèn)證」的處理铸鹰。
例如癌别,我們認(rèn)為用戶更改郵箱地址,是一個(gè)關(guān)鍵操作蹋笼。那么展姐,在發(fā)生這個(gè)操作時(shí),即便用戶已經(jīng)登錄剖毯,系統(tǒng)還是要求用戶重新填寫用戶密碼圾笨,以確認(rèn)修改。后端在收到修改請(qǐng)求后逊谋,產(chǎn)生一個(gè)隨機(jī) number(經(jīng)過(guò)加密運(yùn)算)擂达,作為 permanent cookie 返回給前端,JavaScript 需要讀取這個(gè)值胶滋,并將這個(gè)隨機(jī) number 作為表單 form value 的一項(xiàng)谍婉,它需要隨新的郵箱地址一起提交,服務(wù)端對(duì)這個(gè)隨機(jī) form value 進(jìn)行驗(yàn)證镀钓,驗(yàn)證方式是對(duì)比表單中的 form value 和 cookie 當(dāng)中的隨機(jī) number 是否一致穗熬。
這樣便更大限度地防御了 CSRF 攻擊,流程如下:
我們總結(jié)一下流程丁溅。
Step 1:?jiǎn)雾?yè)應(yīng)用檢查 cookie 中是否存在 JWT payload唤蔗,如果存在,表示用戶已經(jīng)成功進(jìn)行鑒權(quán)窟赏;反之妓柜,重定向到類似 /login 的登錄頁(yè)面。
Step2:用戶在未授權(quán)的情況下涯穷,在登錄頁(yè)面 /login 將用戶名和密碼提交給服務(wù)端棍掐,服務(wù)端返回信息中設(shè)置 authentication cookie,cookie 中含有 JWT 信息拷况。
第二步的具體操作方法可以采用上述第二種和第三種方式作煌,或者增強(qiáng) CSRF 防御的其他手段。
總結(jié)
我們?cè)賮?lái)總結(jié)一下單頁(yè)應(yīng)用進(jìn)行鑒權(quán)的關(guān)鍵問(wèn)題:token 最初由服務(wù)端下發(fā)赚瘦,前端在請(qǐng)求時(shí)需要攜帶粟誓。這樣一來(lái):
如果前端將 JWT 存儲(chǔ)在 localStorage 或者 sessionStorage 當(dāng)中,由于 localStorage 或者 sessionStorage 都可以被 JavaScript 訪問(wèn)起意,如果攻擊者能夠讀取 localStorage 或者 sessionStorage鹰服,那么就能輕易獲取 token,很容易進(jìn)行 XSS 攻擊。
如果將 JWT 存儲(chǔ)在 cookie 當(dāng)中悲酷,我們就可以指定 cookie httpOnly 屬性套菜,來(lái)防止被 JavaScript 讀取,也可以指定 secure 屬性设易,來(lái)保證 JWT 信息只在 HTTPS 下被攜帶笼踩。但是這樣容易遭到 CSRF 攻擊,因此就出現(xiàn)了我們的增強(qiáng)方式亡嫌。
本節(jié)我們通過(guò)分析和設(shè)計(jì)單頁(yè)應(yīng)用鑒權(quán)方案嚎于,熟悉了 JWT 和傳統(tǒng) cookie-session。我們?cè)诮榻B一些安全方面最佳實(shí)踐的同時(shí)挟冠,覆蓋了一些常見的攻擊手段:XSS 和 CSRF 等于购。前端安全是一個(gè)龐大且復(fù)雜的課題,本節(jié)只是通過(guò)一個(gè)比較重要的話題帶大家切入知染,要想全面熟悉前端安全肋僧,完全可以開一門新課了。雖然我的課程志不在此控淡,不過(guò)下面我會(huì)根據(jù)相關(guān)安全話題嫌吠,將我收藏的文章分享給大家。
彩蛋分享
HTTPS 相關(guān)
攻防
同源策略和跨域理論相關(guān)
鑒權(quán)
Getting Token Authentication Right in a Stateless Single Page Application
:現(xiàn)代 Web 應(yīng)用的典型身份驗(yàn)證需求">登錄工程:現(xiàn)代 Web 應(yīng)用的典型身份驗(yàn)證需求