學習HTTP Cookies的工作原理
Cookies是Web的基本組成部分,因為它們允許會話和在導航期間識別用戶
- 介紹
- Cookies的限制
- 設置Cookies
- 設置cookie的到期日期
- 設置cookie的路徑
- 設置cookie的域
- Cookie安全
- 更新cookie的值或參數
- 刪除cookie
- 檢查cookie是否存在
- 抽象庫
- 在服務器端使用cookie
- 使用瀏覽器開發工具檢查cookie
- 替代方案
介紹
通過使用Cookies,我們可以在服務器和瀏覽器之間交換信息,以提供一種自定義用戶會話的方式,以及讓服務器在請求之間識別用戶
HTTP是無狀態的,這意味著所有發往服務器的請求都完全相同,服務器無法確定請求是來自之前已經發出請求的客戶端,還是新的客戶端
瀏覽器在開始HTTP請求時將Cookie發送到服務器,而服務器可以將其發送回來並編輯其內容
Cookies主要用於存儲會話ID。
過去,Cookies用於存儲各種類型的數據,因為當時沒有替代方案。但現在,通過Web存儲API(本地存儲和會話存儲)和IndexedDB,我們有更好的替代方案。
特別是因為Cookies對其可以保存的數據有著非常低的限制,因為它們在每次對服務器的HTTP請求(包括對圖片或CSS / JavaScript文件等資源的請求)都需要來回發送。
Cookies有很長的歷史,它們的第一個版本是在1994年,隨著時間的推移,它們在多個RFC修訂中進行了標準化。
RFC代表“請求評論”,是Internet Engineering Task Force (IETF)定義標準的方式,該組織負責設定網絡的標準
最新的Cookie規範在2011年定義在RFC 6265中。
Cookies的限制
- Cookies只能存儲4KB的數據
- Cookies是特定域的私有,一個網站只能讀取其自己設置的Cookie,不能讀取其他域的Cookie
- 每個域名下的Cookies數量受限制(但具體數量取決於特定瀏覽器的實現)
- Cookies的總數量也受限制(但具體數量取決於特定瀏覽器的實現)。如果超過這個數量,新的Cookies將替換舊的Cookies。
Cookies可以在服務器端或客戶端設置或讀取。
在客戶端,通過文檔對象document object公開Cookies,例如document.cookie
設置Cookies
最簡單的例子是設置一個Cookie:
document.cookie = 'name=Flavio'
這將在已有的Cookie上添加一個新的Cookie(不會覆蓋現有的Cookie)
Cookie的值應該使用encodeURIComponent()
進行URL編碼,以確保它不包含任何空格、逗號或分號,這些在Cookie值中是無效的。
設置cookie的到期日期
如果您沒有設置其他內容,該cookie將在瀏覽器關閉時過期。為了防止這種情況,可以添加一個到期日期,以UTC格式表示(Mon, 26 Mar 2018 17:04:05 UTC
)
document.cookie = 'name=Flavio; expires=Mon, 26 Mar 2018 17:04:05 UTC'
設置一個在24小時內到期的cookie的簡單JavaScript片段如下:
const date = new Date()
date.setHours(date.getHours() + 24)
document.cookie = 'name=Flavio; expires=' + date.toUTCString()
或者可以使用max-age
參數來設置以秒為單位的到期時間:
document.cookie = 'name=Flavio; max-age=3600' // 60分鐘後到期
document.cookie = 'name=Flavio; max-age=31536000' // 1年後到期
設置cookie的路徑
path
參數指定cookie的文件位置,因此它僅分配給特定的路徑,並且只有在路徑與當前的文件位置或父文件位置匹配時,才會將其發送到服務器:
document.cookie = 'name=Flavio; path=/dashboard'
此cookie將在/dashboard
,/dashboard/today
和其他/dashboard/
的子URL上發送,但在/posts
上不會發送。
如果未設置路徑,則默認為當前的文件位置。這意味著要從內部頁面上應用全局cookie,需要指定path=/
。
設置cookie的域
domain
可以用於為cookie指定子域。
document.cookie = 'name=Flavio; domain=mysite.com;'
如果未設置,則默認為主機,即使使用子域(如果在subdomain.mydomain.com上,默認為mydomain.com)。域cookie包含在子域中。
Cookie安全
Secure
添加Secure
參數確保cookie只能通過HTTPS安全傳輸,而不會在未加密的HTTP連接中發送:
document.cookie = 'name=Flavio; Secure;'
請注意,這並不意味著以任何方式使cookie變得安全 - 請始終避免向cookie添加敏感信息
HttpOnly
一個有用的參數是HttpOnly
,它使cookie無法通過document.cookie
API訪問,因此它們只能由服務器編輯:
document.cookie = 'name=Flavio; Secure; HttpOnly'
SameSite
SameSite
(不幸的是,仍然不是所有瀏覽器都支持,但許多瀏覽器支持! https://caniuse.com/#feat=same-site-cookie-attribute)允許服務器要求cookie不在跨站請求中發送,只在具有與cookie域相匹配的原始URL的資源上發送,這應該對減少CSRF(跨站請求偽造)攻擊的風險有很大幫助。
更新cookie的值或參數
要更新cookie的值,只需將新值分配給cookie名:
document.cookie = 'name=Flavio2'
類似於更新值,要更新到期日期,重新分配具有新的expires
或max-age
屬性的值:
document.cookie = 'name=Flavio; max-age=31536000' // 1年後到期
只需記住還要添加任何您在第一次設置時添加的其他參數,例如path
或domain
。
刪除cookie
要刪除cookie,請取消設置它的值並傳遞過去的日期:
document.cookie = 'name=; expires=Thu, 01 Jan 1970 00:00:00 UTC;'
(同樣,使用設置cookie時使用的所有參數)
訪問cookie的值
要訪問cookie,查找document.cookie
:
const cookies = document.cookie
這將返回包含頁面上設置的所有cookie的字符串,用分號分隔:
'name1=Flavio1; name2=Flavio2; name3=Flavio3'
檢查cookie是否存在
//ES5
if (
document.cookie.split(';').filter(item => {
return item.indexOf('name=') >= 0
}).length
) {
//name exists
}
//ES2016
if (
document.cookie.split(';').filter(item => {
return item.includes('name=')
}).length
) {
//name exists
}
抽象庫
有許多不同的庫可以提供更友好的API來管理cookie。其中之一是https://github.com/js-cookie/js-cookie,它支持到IE7,並在GitHub上獲得了很多星星(這總是好的)。
以下是一些用法示例:
Cookies.set('name', 'value')
Cookies.set('name', 'value', {
expires: 7,
path: '',
domain: 'subdomain.site.com',
secure: true
})
Cookies.get('name') // => 'value'
Cookies.remove('name')
//JSON
Cookies.set('name', { name: 'Flavio' })
Cookies.getJSON('name') // => { name: 'Flavio' }
是使用本地庫還是使用原生的Cookies API?
這取決於您是否願意為每個用戶下載更多的千字節,所以由您自行選擇。
在服務器端使用cookies
用於構建HTTP服務器的每個環境都允許您與cookie交互,因為cookie是現代Web的基石,沒有它們就不能構建太多東西。
PHP有$_COOKIE
Go在net/http
標准庫中有cookie功能
等等。
讓我們使用Node.js編寫一個示例
在使用Express.js時,可以使用res.cookie
API創建cookie:
res.cookie('name1', '1Flavio', {
domain: '.example.com',
path: '/admin',
secure: true
})
res.cookie('name2', 'Flavio2', {
expires: new Date(Date.now() + 900000),
httpOnly: true
})
res.cookie('name3', 'Flavio3', { maxAge: 900000, httpOnly: true })
//將JSON序列化到cookie
res.cookie('name4', { items: [1, 2, 3] }, { maxAge: 900000 })
要解析cookie,一個好的選擇是使用https://github.com/expressjs/cookie-parser中間件。每個Request對象在req.cookie
屬性中都包含cookie信息:
req.cookies.name //Flavio
req.cookies.name1 //Flavio1
如果使用signed: true
標誌創建cookie:
res.cookie('name5', 'Flavio5', { signed: true })
它們將在req.signedCookies
對象中可用。簽名cookie受到客戶端修改的保護。用於簽署cookie值的簽名確保您可以在服務器端知道客戶端是否已對其進行了修改。
https://github.com/expressjs/session和https://github.com/expressjs/cookie-session是兩個不同的中間件選項,用於構建基於cookie的身份驗證,使用哪一個取決於您的需求。
使用瀏覽器開發工具檢查cookie
所有瀏覽器都在其開發工具中提供了一個界面來檢查和編輯cookie。
Chrome
Firefox
Safari
替代方案
Cookie是Web上構建身份驗證和會話的唯一方式嗎?
不是!近來變得很受歡迎的一種技術是JSON Web Tokens(JWT),這是一種基於令牌的身份驗證。