/

`Secure`

學習HTTP Cookies的工作原理

Cookies是Web的基本組成部分,因為它們允許會話和在導航期間識別用戶

介紹

通過使用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:

1
document.cookie = 'name=Flavio'

這將在已有的Cookie上添加一個新的Cookie(不會覆蓋現有的Cookie)

Cookie的值應該使用encodeURIComponent()進行URL編碼,以確保它不包含任何空格、逗號或分號,這些在Cookie值中是無效的。

設置cookie的到期日期

如果您沒有設置其他內容,該cookie將在瀏覽器關閉時過期。為了防止這種情況,可以添加一個到期日期,以UTC格式表示(Mon, 26 Mar 2018 17:04:05 UTC

1
document.cookie = 'name=Flavio; expires=Mon, 26 Mar 2018 17:04:05 UTC'

設置一個在24小時內到期的cookie的簡單JavaScript片段如下:

1
2
3
const date = new Date()
date.setHours(date.getHours() + 24)
document.cookie = 'name=Flavio; expires=' + date.toUTCString()

或者可以使用max-age參數來設置以秒為單位的到期時間:

1
2
document.cookie = 'name=Flavio; max-age=3600' // 60分鐘後到期
document.cookie = 'name=Flavio; max-age=31536000' // 1年後到期

設置cookie的路徑

path參數指定cookie的文件位置,因此它僅分配給特定的路徑,並且只有在路徑與當前的文件位置或父文件位置匹配時,才會將其發送到服務器:

1
document.cookie = 'name=Flavio; path=/dashboard'

此cookie將在/dashboard/dashboard/today和其他/dashboard/的子URL上發送,但在/posts上不會發送。

如果未設置路徑,則默認為當前的文件位置。這意味著要從內部頁面上應用全局cookie,需要指定path=/

設置cookie的域

domain可以用於為cookie指定子域。

1
document.cookie = 'name=Flavio; domain=mysite.com;'

如果未設置,則默認為主機,即使使用子域(如果在subdomain.mydomain.com上,默認為mydomain.com)。域cookie包含在子域中。

Cookie安全

Secure

添加Secure參數確保cookie只能通過HTTPS安全傳輸,而不會在未加密的HTTP連接中發送:

1
document.cookie = 'name=Flavio; Secure;'

請注意,這並不意味著以任何方式使cookie變得安全 - 請始終避免向cookie添加敏感信息

HttpOnly

一個有用的參數是HttpOnly,它使cookie無法通過document.cookie API訪問,因此它們只能由服務器編輯:

1
document.cookie = 'name=Flavio; Secure; HttpOnly'

SameSite

SameSite(不幸的是,仍然不是所有瀏覽器都支持,但許多瀏覽器支持! https://caniuse.com/#feat=same-site-cookie-attribute)允許服務器要求cookie不在跨站請求中發送,只在具有與cookie域相匹配的原始URL的資源上發送,這應該對減少CSRF(跨站請求偽造)攻擊的風險有很大幫助。

更新cookie的值或參數

要更新cookie的值,只需將新值分配給cookie名:

1
document.cookie = 'name=Flavio2'

類似於更新值,要更新到期日期,重新分配具有新的expiresmax-age屬性的值:

1
document.cookie = 'name=Flavio; max-age=31536000' // 1年後到期

只需記住還要添加任何您在第一次設置時添加的其他參數,例如pathdomain

刪除cookie

要刪除cookie,請取消設置它的值並傳遞過去的日期:

1
document.cookie = 'name=; expires=Thu, 01 Jan 1970 00:00:00 UTC;'

(同樣,使用設置cookie時使用的所有參數)

訪問cookie的值

要訪問cookie,查找document.cookie

1
const cookies = document.cookie

這將返回包含頁面上設置的所有cookie的字符串,用分號分隔:

1
'name1=Flavio1; name2=Flavio2; name3=Flavio3'

檢查cookie是否存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//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上獲得了很多星星(這總是好的)。

以下是一些用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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信息:

1
2
req.cookies.name //Flavio
req.cookies.name1 //Flavio1

如果使用signed: true標誌創建cookie:

1
res.cookie('name5', 'Flavio5', { signed: true })

它們將在req.signedCookies對象中可用。簽名cookie受到客戶端修改的保護。用於簽署cookie值的簽名確保您可以在服務器端知道客戶端是否已對其進行了修改。

https://github.com/expressjs/sessionhttps://github.com/expressjs/cookie-session是兩個不同的中間件選項,用於構建基於cookie的身份驗證,使用哪一個取決於您的需求。

使用瀏覽器開發工具檢查cookie

所有瀏覽器都在其開發工具中提供了一個界面來檢查和編輯cookie。

Chrome

Chrome開發者工具中的cookie

Firefox

Firefox開發工具中的cookie

Safari

Safari開發工具中的cookie

替代方案

Cookie是Web上構建身份驗證和會話的唯一方式嗎?

不是!近來變得很受歡迎的一種技術是JSON Web TokensJWT),這是一種基於令牌的身份驗證