[TOC]
# 簡介
前后端分離通過 Restful API 進行數據交互時,如何驗證用戶的登錄信息及權限。
由于 HTTP 協定是不儲存狀態的 (stateless),這意味著當我們透過帳號密碼驗證一個使用者時,當下一個 request 請求時它就把剛剛的資料忘了。于是我們的程序就不知道誰是誰,就要再驗證一次。
所以為了保證系統安全,我們就需要驗證用戶否處于登錄狀態。
# 傳統方式
前端登錄,后端根據用戶信息生成一個 token,并保存這個 token 和對應的用戶 id 到數據庫或 Session 中,接著把 token 傳給用戶,存入瀏覽器 cookie,之后**瀏覽器請求帶上這個 cookie**,后端根據這個 cookie 值來查詢用戶,驗證是否過期。
## 缺點
1. XSS 漏洞
如果我們的頁面出現了 XSS 漏洞,由于 cookie 可以被 JavaScript 讀取,XSS 漏洞會導致用戶 token 泄露,而作為后端識別用戶的標識,cookie 的泄露意味著用戶信息不再安全。
盡管我們通過轉義輸出內容,使用 CDN 等可以盡量避免 XSS 注入,但誰也不能保證在大型的項目中不會出現這個問題。
其實你**可以設置 httpOnly 以及 secure 項**。
1. 設置 httpOnly 后 cookie 將不能被 JS 讀取,瀏覽器會自動的把它加在請求的 header 當中;
2. 設置 secure 的話,cookie 就只允許通過 HTTPS 傳輸。secure 選項可以過濾掉一些使用 HTTP 協議的 XSS 注入,但并不能完全阻止。
2. 服務器端的壓力
如果將驗證信息保存在數據庫中,后端每次都需要根據 token 查出用戶 id,這就增加了數據庫的查詢和存儲開銷。若把驗證信息保存在 session 中,又加大了服務器端的存儲壓力。
## 疑問???
httpOnly 選項使得 JS 不能讀取到 cookie,那么 XSS 注入的問題也基本不用擔心了。
但設置 httpOnly 就帶來了另一個問題,就是很容易的被 XSRF,即跨站請求偽造。
當你瀏覽器開著這個頁面的時候,另一個頁面可以很容易的跨站請求這個頁面的內容。因為 cookie 默認被發了出去。
那我們可不可以**不要服務器去查詢呢**?
如果我們生成 token 遵循一定的規律,比如我們使用對稱加密算法來加密用戶 id 形成 token,那么服務端以后其實只要解密該 token 就可以知道用戶的 id 是什么了。不過呢,我只是舉個例子而已,要是真這么做,只要你的對稱加密算法泄露了,其他人可以通過這種加密方式進行偽造 token,那么所有用戶信息都不再安全了。恩,那用非對稱加密算法來做呢,其實現在有個規范就是這樣做的,接下來要介紹的 JWT。
# JWT 介紹
JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案。
它定義了一種簡潔,自包含的用于通信雙方之間以 JSON 對象的形式安全傳遞信息的方法。
JWT 可以使用 HMAC 算法或者是 RSA 的公鑰密鑰對進行簽名。
它具備兩個特點:
* 簡潔(Compact)
可以通過 URL,POST 參數或者在 HTTP header 發送,因為數據量小,傳輸速度快
* 自包含(Self-contained)
負載中包含了所有用戶所需要的信息,避免了多次查詢數據庫
# JWT 組成
JWT 的三個部分依次如下:
* Header(頭部)
* 進行 base64 編碼
* Payload(負載)
* 進行 base64 編碼
* 因為 `base64` 是對稱解密的,意味著該部分信息可以歸類為明文信息。
* Signature(簽名)
* 將 Header 和 Payload 通過密鑰 secret 和加鹽算法進行加密后生成
寫成一行,就是下面的樣子:
```
Header.Payload.Signature
// 示例 Token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiJ9.Qjw1epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U
```
如果你還想確認這些信息是否真的存在,可以拷貝 JWT 串到 http://jwt.io 的在線校驗工具校驗一下即可。
其實到這一步可能就有人會想了,HTTP 請求總會帶上 token,這樣這個 token 傳來傳去占用不必要的帶寬啊。
其實相比 cookie ,token是可選擇的攜帶在 header 頭上;
還有可以去了解下 HTTP2,HTTP2 對頭部進行了壓縮,相信也解決了這個問題。

# JWT 使用

1. 首先,前端通過 Web 表單將自己的用戶名和密碼發送到后端的接口。這一過程一般是一個 HTTP POST 請求。建議的方式是通過 SSL 加密的傳輸(https 協議),從而避免敏感信息被嗅探。
2. 后端核對用戶名和密碼成功后,將用戶的 id 等其他信息作為 JWT Payload(負載),將其與頭部分別進行 Base64 編碼拼接后簽名,形成一個 JWT。形成的 JWT 就是一個形同 `hhh.ppp.sss` 的字符串。
3. 后端將 JWT 字符串作為登錄成功的返回結果返回給前端。**前端可以將返回的結果保存在 `localStorage` 或 `sessionStorage` 上,退出登錄時前端刪除保存的 JWT 即可**。
4. 前端在每次請求時將 JWT 放入 HTTP Header 中的 `Authorization` 位。(解決 XSS 和 XSRF 問題)
```
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token // JWT 規定的的表示形式
}
})
```
5. 后端檢查是否存在,如存在驗證 JWT 的有效性。例如,檢查簽名是否正確;檢查 Token 是否過期;檢查 Token 的接收方是否是自己(可選)。
服務器將用戶信息和自己的密鑰通過既定好的算法進行簽名,然后將發來的簽名和生成的簽名比較,嚴格相等則說明用戶信息沒被篡改和偽造,驗證通過。
6. 驗證通過后后端使用 JWT 中包含的用戶信息進行其他邏輯操作,返回相應結果。
## 和 Session 方式存儲 id 的差異
Session 方式存儲用戶 id 的最大弊病在于 Session 是存儲在服務器端的,所以需要占用大量服務器內存,對于較大型應用而言可能還要保存許多的狀態。一般而言,大型應用還需要借助一些 KV 數據庫和一系列緩存機制來實現 Session 的存儲。
1. JWT 方式將用戶狀態分散到了客戶端中,可以明顯減輕服務端的 內存壓力。
2. JWT 方式讓服務器有一些計算壓力(例如加密、編碼和解碼),但是這些壓力相比磁盤存儲而言可能就不算什么了。
除了用戶 id 之外,還可以存儲其他的和用戶相關的信息,例如該用戶是否是管理員、用戶所在的分組等。
具體是否采用,需要在不同場景下用數據說話。
## 單點登錄(SSO)
Session 方式來存儲用戶 id,一開始用戶的 Session 只會存儲在一臺服務器上。
對于有多個子域名的站點,每個子域名至少會對應一臺不同的服務器,例如:`www.taobao.com`,`nv.taobao.com`,`nz.taobao.com`,`login.taobao.com`。
所以如果要實現在`login.taobao.com`登錄后,在其他的子域名下依然可以取到 Session,這要求我們在多臺服務器上同步 Session。使用 JWT 的方式則不會存在這個問題,因為用戶的狀態已經被傳送到了客戶端。
# 總結
## 優點
因為 json 的通用性,所以 JWT 是可以進行跨語言支持的,像 JAVA、JavaScript、NodeJS、PHP 等很多語言都可以使用。
因為有了 payload 部分,所以 JWT 可以在自身存儲一些其他業務邏輯所必須的非敏感信息。
便于傳輸,jwt 的構成非常簡單,字節占用很小,所以它是非常便于傳輸的。
它不需要在服務端保存會話信息,所以它易于應用的擴展。
## 安全相關
1. 不應該在 jwt 的 payload 部分存放敏感信息,因為該部分是客戶端可解密的部分。
2. 保護好 secret 私鑰,該私鑰非常重要。
3. 如果可以,請使用 https 協議。
# 其他資料
[基于 JWT 的 Token 認證的安全問題](https://www.cnblogs.com/ypppt/p/13332007.html)
[基于 Token 的身份驗證](https://ninghao.net/blog/2834)
[Vue刷新token,判斷token是否過期、失效的最簡便的方法](https://blog.csdn.net/weixin_40667613/article/details/90639614)
# 參考
[通過一個案例理解 JWT](https://juejin.im/post/5ba37c50e51d450e664b3fc3)
[前后端分離之 JWT 用戶認證](https://lion1ou.win/2017/01/18/)
[前后端分離, 前端如何防止直接輸入 URL 進入頁面?](https://www.zhihu.com/question/269101275)
[JSON Web Token 入門教程](https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html)
[前后端分離模式下,如何跟蹤用戶狀態?](https://blog.csdn.net/hwhsong/article/details/82020526)
- 講解 Markdown
- 示例
- SVN
- Git筆記
- github 相關
- DESIGNER'S GUIDE TO DPI
- JS 模塊化
- CommonJS、AMD、CMD、UMD、ES6
- AMD
- RequrieJS
- r.js
- 模塊化打包
- 學習Chrome DevTools
- chrome://inspect
- Chrome DevTools 之 Elements
- Chrome DevTools 之 Console
- Chrome DevTools 之 Sources
- Chrome DevTools 之 Network
- Chrome DevTools 之 Memory
- Chrome DevTools 之 Performance
- Chrome DevTools 之 Resources
- Chrome DevTools 之 Security
- Chrome DevTools 之 Audits
- 技巧
- Node.js
- 基礎知識
- package.json 詳解
- corepack
- npm
- yarn
- pnpm
- yalc
- 庫處理
- Babel
- 相關庫
- 轉譯基礎
- 插件
- AST
- Rollup
- 基礎
- 插件
- Webpack
- 詳解配置
- 實現 loader
- webpack 進階
- plugin 用法
- 輔助工具
- 解答疑惑
- 開發工具集合
- 花樣百出的打包工具
- 紛雜的構建系統
- monorepo
- 前端工作流
- 爬蟲
- 測試篇
- 綜合
- Jest
- playwright
- Puppeteer
- cypress
- webdriverIO
- TestCafe
- 其他
- 工程開發
- gulp篇
- Building With Gulp
- Sass篇
- PostCSS篇
- combo服務
- 編碼規范檢查
- 前端優化
- 優化策略
- 高性能HTML5
- 瀏覽器端性能
- 前后端分離篇
- 分離部署
- API 文檔框架
- 項目開發環境
- 基于 JWT 的 Token 認證
- 扯皮時間
- 持續集成及后續服務
- 靜態服務器搭建
- mock與調試
- browserslist
- Project Starter
- Docker
- 文檔網站生成
- ddd