49.不可忽視的前端安全 - 單頁(yè)應(yīng)用鑒權(quán)設(shè)計(jì)

安全是計(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 好的文章:

不要使用 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)

CDN 劫持和其他安全問(wèn)題

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市尼桶,隨后出現(xiàn)的幾起案子操灿,更是在濱河造成了極大的恐慌,老刑警劉巖泵督,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異庶喜,居然都是意外死亡小腊,警方通過(guò)查閱死者的電腦和手機(jī)救鲤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秩冈,“玉大人本缠,你說(shuō)我怎么就攤上這事∪胛剩” “怎么了丹锹?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)芬失。 經(jīng)常有香客問(wèn)我楣黍,道長(zhǎng),這世上最難降的妖魔是什么棱烂? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任租漂,我火速辦了婚禮,結(jié)果婚禮上颊糜,老公的妹妹穿的比我還像新娘哩治。我一直安慰自己,他們只是感情好衬鱼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布业筏。 她就那樣靜靜地躺著,像睡著了一般鸟赫。 火紅的嫁衣襯著肌膚如雪驾孔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天惯疙,我揣著相機(jī)與錄音翠勉,去河邊找鬼。 笑死霉颠,一個(gè)胖子當(dāng)著我的面吹牛对碌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蒿偎,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朽们,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了诉位?” 一聲冷哼從身側(cè)響起骑脱,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎苍糠,沒(méi)想到半個(gè)月后叁丧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年拥娄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蚊锹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稚瘾,死狀恐怖牡昆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情摊欠,我是刑警寧澤丢烘,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站些椒,受9級(jí)特大地震影響播瞳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜摊沉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一狐史、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧说墨,春花似錦骏全、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至棺棵,卻和暖如春楼咳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背烛恤。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工母怜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缚柏。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓苹熏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親币喧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子轨域,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 一. 瀏覽器安全策略 同源策略 瀏覽器的安全都是以同源為基礎(chǔ),它是瀏覽器最核心也最基本的安全功能 同源策略規(guī)定:不...
    菊花泡茶閱讀 528評(píng)論 0 0
  • 以下題目是根據(jù)網(wǎng)上多份面經(jīng)收集而來(lái)的杀餐,題目相同意味著被問(wèn)的頻率比較高干发,有問(wèn)題歡迎留言討論,喜歡可以點(diǎn)贊關(guān)注史翘。 以下...
    Aniugel閱讀 14,740評(píng)論 1 6
  • 前言開門見山枉长,這篇文章冀续,適合 「 初級(jí)前端 」 ,如果你還在校招的話搀暑,或者還在求職的話沥阳,可以看看本文跨琳,找一找靈感自点,...
    WEB前端含光閱讀 1,343評(píng)論 1 2
  • 1. 認(rèn)證 (Authentication) 和授權(quán) (Authorization)的區(qū)別是什么? 這是一個(gè)絕大多...
    前端三少爺閱讀 344評(píng)論 0 0
  • 以下題目是根據(jù)網(wǎng)上多份面經(jīng)收集而來(lái)的脉让,題目相同意味著被問(wèn)的頻率比較高桂敛,有問(wèn)題歡迎留言討論,喜歡可以點(diǎn)贊關(guān)注溅潜。 1术唬、...
    lessonSam閱讀 305評(píng)論 0 0