HTTP 請(qǐng)求都是無(wú)狀態(tài)的,但是我們的 Web 應(yīng)用通常都需要知道發(fā)起請(qǐng)求的人是誰(shuí)。為了解決這個(gè)問(wèn)題,HTTP 協(xié)議設(shè)計(jì)了一個(gè)特殊的請(qǐng)求頭:Cookie。服務(wù)端可以通過(guò)響應(yīng)頭(set-cookie)將少量數(shù)據(jù)響應(yīng)給客戶(hù)端,瀏覽器會(huì)遵循協(xié)議將數(shù)據(jù)保存,并在下次請(qǐng)求同一個(gè)服務(wù)的時(shí)候帶上(瀏覽器也會(huì)遵循協(xié)議,只在訪問(wèn)符合 Cookie 指定規(guī)則的網(wǎng)站時(shí)帶上對(duì)應(yīng)的 Cookie 來(lái)保證安全性)。
通過(guò) ctx.cookies,我們可以在 controller 中便捷、安全的設(shè)置和讀取 Cookie。
class HomeController extends Controller { |
設(shè)置 Cookie 其實(shí)是通過(guò)在 HTTP 響應(yīng)中設(shè)置 set-cookie 頭完成的,每一個(gè) set-cookie 都會(huì)讓瀏覽器在 Cookie 中存一個(gè)鍵值對(duì)。在設(shè)置 Cookie 值的同時(shí),協(xié)議還支持許多參數(shù)來(lái)配置這個(gè) Cookie 的傳輸、存儲(chǔ)和權(quán)限。
除了這些屬性之外,框架另外擴(kuò)展了 3 個(gè)參數(shù)的支持:
在設(shè)置 Cookie 時(shí)我們需要思考清楚這個(gè) Cookie 的作用,它需要被瀏覽器保存多久?是否可以被 js 獲取到?是否可以被前端修改?
默認(rèn)的配置下,Cookie 是加簽不加密的,瀏覽器可以看到明文,js 不能訪問(wèn),不能被客戶(hù)端(手工)篡改。
ctx.cookies.set(key, value, { |
ctx.cookies.set(key, value, { |
注意:
由于 HTTP 請(qǐng)求中的 Cookie 是在一個(gè) header 中傳輸過(guò)來(lái)的,通過(guò)框架提供的這個(gè)方法可以快速的從整段 Cookie 中獲取對(duì)應(yīng)的鍵值對(duì)的值。上面在設(shè)置 Cookie 的時(shí)候,我們可以設(shè)置 options.signed 和 options.encrypt 來(lái)對(duì) Cookie 進(jìn)行簽名或加密,因此對(duì)應(yīng)的在獲取 Cookie 的時(shí)候也要傳相匹配的選項(xiàng)。
如果要獲取前端或者其他系統(tǒng)設(shè)置的 cookie,需要指定參數(shù) signed 為 false,避免對(duì)它做驗(yàn)簽導(dǎo)致獲取不到 cookie 的值。
ctx.cookies.get('frontend-cookie', { |
由于我們?cè)?Cookie 中需要用到加解密和驗(yàn)簽,所以需要配置一個(gè)秘鑰供加密使用。在 config/config.default.js 中
module.exports = { |
keys 配置成一個(gè)字符串,可以按照逗號(hào)分隔配置多個(gè) key。Cookie 在使用這個(gè)配置進(jìn)行加解密時(shí):
如果我們想要更新 Cookie 的秘鑰,但是又不希望之前設(shè)置到用戶(hù)瀏覽器上的 Cookie 失效,可以將新的秘鑰配置到 keys 最前面,等過(guò)一段時(shí)間之后再刪去不需要的秘鑰即可。
Cookie 在 Web 應(yīng)用中經(jīng)常承擔(dān)標(biāo)識(shí)請(qǐng)求方身份的功能,所以 Web 應(yīng)用在 Cookie 的基礎(chǔ)上封裝了 Session 的概念,專(zhuān)門(mén)用做用戶(hù)身份識(shí)別。
框架內(nèi)置了 Session 插件,給我們提供了 ctx.session 來(lái)訪問(wèn)或者修改當(dāng)前用戶(hù) Session 。
class HomeController extends Controller { |
Session 的使用方法非常直觀,直接讀取它或者修改它就可以了,如果要?jiǎng)h除它,直接將它賦值為 null:
ctx.session = null; |
需要 特別注意 的是:設(shè)置 session 屬性時(shí)需要避免以下幾種情況(會(huì)造成字段丟失,詳見(jiàn) koa-session 源碼)
// ? 錯(cuò)誤的用法 |
Session 的實(shí)現(xiàn)是基于 Cookie 的,默認(rèn)配置下,用戶(hù) Session 的內(nèi)容加密后直接存儲(chǔ)在 Cookie 中的一個(gè)字段中,用戶(hù)每次請(qǐng)求我們網(wǎng)站的時(shí)候都會(huì)帶上這個(gè) Cookie,我們?cè)诜?wù)端解密后使用。Session 的默認(rèn)配置如下:
exports.session = { |
可以看到這些參數(shù)除了 key 都是 Cookie 的參數(shù),key 代表了存儲(chǔ) Session 的 Cookie 鍵值對(duì)的 key 是什么。在默認(rèn)的配置下,存放 Session 的 Cookie 將會(huì)加密存儲(chǔ)、不可被前端 js 訪問(wèn),這樣可以保證用戶(hù)的 Session 是安全的。
Session 默認(rèn)存放在 Cookie 中,但是如果我們的 Session 對(duì)象過(guò)于龐大,就會(huì)帶來(lái)一些額外的問(wèn)題:
框架提供了將 Session 存儲(chǔ)到除了 Cookie 之外的其他存儲(chǔ)的擴(kuò)展方案,我們只需要設(shè)置 app.sessionStore 即可將 Session 存儲(chǔ)到指定的存儲(chǔ)中。
// app.js |
sessionStore 的實(shí)現(xiàn)我們也可以封裝到插件中,例如 egg-session-redis 就提供了將 Session 存儲(chǔ)到 redis 中的能力,在應(yīng)用層,我們只需要引入 egg-redis 和 egg-session-redis 插件即可。
// plugin.js |
注意:一旦選擇了將 Session 存入到外部存儲(chǔ)中,就意味著系統(tǒng)將強(qiáng)依賴(lài)于這個(gè)外部存儲(chǔ),當(dāng)它掛了的時(shí)候,我們就完全無(wú)法使用 Session 相關(guān)的功能了。因此我們更推薦大家只將必要的信息存儲(chǔ)在 Session 中,保持 Session 的精簡(jiǎn)并使用默認(rèn)的 Cookie 存儲(chǔ),用戶(hù)級(jí)別的緩存不要存儲(chǔ)在 Session 中。
雖然在 Session 的配置中有一項(xiàng)是 maxAge,但是它只能全局設(shè)置 Session 的有效期,我們經(jīng)??梢栽谝恍┚W(wǎng)站的登陸頁(yè)上看到有 記住我 的選項(xiàng)框,勾選之后可以讓登陸用戶(hù)的 Session 有效期更長(zhǎng)。這種針對(duì)特定用戶(hù)的 Session 有效時(shí)間設(shè)置我們可以通過(guò) ctx.session.maxAge= 來(lái)實(shí)現(xiàn)。
const ms = require('ms'); |
默認(rèn)情況下,當(dāng)用戶(hù)請(qǐng)求沒(méi)有導(dǎo)致 Session 被修改時(shí),框架都不會(huì)延長(zhǎng) Session 的有效期,但是在有些場(chǎng)景下,我們希望用戶(hù)如果長(zhǎng)時(shí)間都在訪問(wèn)我們的站點(diǎn),則延長(zhǎng)他們的 Session 有效期,不讓用戶(hù)退出登錄態(tài)??蚣芴峁┝艘粋€(gè) renew 配置項(xiàng)用于實(shí)現(xiàn)此功能,它會(huì)在發(fā)現(xiàn)當(dāng)用戶(hù) Session 的有效期僅剩下最大有效期一半的時(shí)候,重置 Session 的有效期。
// config/config.default.js |
更多建議: