W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
Cookie 是直接存儲在瀏覽器中的一小串數(shù)據(jù)。它們是 HTTP 協(xié)議的一部分,由 RFC 6265 規(guī)范定義。
Cookie 通常是由 Web 服務器使用響應 Set-Cookie
HTTP-header 設置的。然后瀏覽器使用 Cookie
HTTP-header 將它們自動添加到(幾乎)每個對相同域的請求中。
最常見的用處之一就是身份驗證:
Set-Cookie
? HTTP-header 來設置具有唯一“會話標識符(session identifier)”的 cookie。Cookie
? HTTP-header 通過網(wǎng)絡發(fā)送 cookie。我們還可以使用 document.cookie
屬性從瀏覽器訪問 cookie。
關于 cookie 及其選項,有很多棘手的事情。在本章中,我們將詳細介紹它們。
你的瀏覽器是否存儲了本網(wǎng)站的任何 cookie?讓我們來看看:
// 在 javascript.info,我們使用谷歌分析來進行統(tǒng)計,
// 所以應該存在一些 cookie
alert( document.cookie ); // cookie1=value1; cookie2=value2;...
document.cookie
的值由 name=value
對組成,以 ;
分隔。每一個都是獨立的 cookie。
為了找到一個特定的 cookie,我們可以以 ;
作為分隔,將 document.cookie
分開,然后找到對應的名字。我們可以使用正則表達式或者數(shù)組函數(shù)來實現(xiàn)。
我們把這個留給讀者當作練習。此外,在本章的最后,你可以找到一些操作 cookie 的輔助函數(shù)。
我們可以寫入 document.cookie
。但這不是一個數(shù)據(jù)屬性,它是一個 訪問器(getter/setter)。對其的賦值操作會被特殊處理。
對 document.cookie
的寫入操作只會更新其中提到的 cookie,而不會涉及其他 cookie。
例如,此調(diào)用設置了一個名稱為 user
且值為 John
的 cookie:
document.cookie = "user=John"; // 只會更新名稱為 user 的 cookie
alert(document.cookie); // 展示所有 cookie
如果你運行了上面這段代碼,你會看到多個 cookie。這是因為 document.cookie=
操作不是重寫整所有 cookie。它只設置代碼中提到的 cookie user
。
從技術(shù)上講,cookie 的名稱和值可以是任何字符。為了保持有效的格式,它們應該使用內(nèi)建的 encodeURIComponent
函數(shù)對其進行轉(zhuǎn)義:
// 特殊字符(空格),需要編碼
let name = "my name";
let value = "John Smith"
// 將 cookie 編碼為 my%20name=John%20Smith
document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value);
alert(document.cookie); // ...; my%20name=John%20Smith
限制
存在一些限制:
- ?
encodeURIComponent
? 編碼后的 ?name=value
? 對,大小不能超過 4KB。因此,我們不能在一個 cookie 中保存大的東西。- 每個域的 cookie 總數(shù)不得超過 20+ 左右,具體限制取決于瀏覽器。
Cookie 有幾個選項,其中很多都很重要,應該設置它。
選項被列在 key=value
之后,以 ;
分隔,像這樣:
document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT"
path=/mypath
?url 路徑前綴必須是絕對路徑。它使得該路徑下的頁面可以訪問該 cookie。默認為當前路徑。
如果一個 cookie 帶有 path=/admin
設置,那么該 cookie 在 /admin
和 /admin/something
下都是可見的,但是在 /home
或 /adminpage
下不可見。
通常,我們應該將 path
設置為根目錄:path=/
,以使 cookie 對此網(wǎng)站的所有頁面可見。
domain=site.com
?domain 控制了可訪問 cookie 的域。但是在實際中,有一些限制。我們無法設置任何域。
無法從另一個二級域訪問 cookie,因此 other.com
永遠不會收到在 site.com
設置的 cookie。
這是一項安全限制,為了允許我們將敏感數(shù)據(jù)存儲在應該僅在一個站點上可用的 cookie 中。
默認情況下,cookie 只有在設置的域下才能被訪問到。
請注意,默認情況下,cookie 也不會共享給子域,例如 ?forum.site.com
?。
// 如果我們在 site.com 網(wǎng)站上設置了 cookie……
document.cookie = "user=John"
// ……在 forum.site.com 域下我們無法訪問它
alert(document.cookie); // 沒有 user
……但這是可以設置的。如果我們想允許像 forum.site.com
這樣的子域在 site.com
上設置 cookie,也是可以實現(xiàn)的。
為此,當在 site.com
設置 cookie 時,我們應該明確地將 domain
選項設置為根域:domain=site.com
。那么,所有子域都可以訪問到這樣的 cookie。
例如:
// 在 site.com
// 使 cookie 可以被在任何子域 *.site.com 訪問:
document.cookie = "user=John; domain=site.com"
// 之后
// 在 forum.site.com
alert(document.cookie); // 有 cookie user=John
出于歷史原因,domain=.site.com
(site.com
前面有一個點符號)也以相同的方式工作,允許從子域訪問 cookie。這是一個舊的表示方式,如果我們需要支持非常舊的瀏覽器,那么應該使用它。
總結(jié)一下,通過 domain
選項的設置,可以實現(xiàn)允許在子域訪問 cookie。
默認情況下,如果一個 cookie 沒有設置這兩個參數(shù)中的任何一個,那么在關閉瀏覽器之后,它就會消失。此類 cookie 被稱為 "session cookie”。
為了讓 cookie 在瀏覽器關閉后仍然存在,我們可以設置 expires
或 max-age
選項中的一個。
expires=Tue, 19 Jan 2038 03:14:07 GMT
?cookie 的過期時間定義了瀏覽器會自動清除該 cookie 的時間。
日期必須完全采用 GMT 時區(qū)的這種格式。我們可以使用 date.toUTCString
來獲取它。例如,我們可以將 cookie 設置為 1 天后過期。
// 當前時間 +1 天
let date = new Date(Date.now() + 86400e3);
date = date.toUTCString();
document.cookie = "user=John; expires=" + date;
如果我們將 expires
設置為過去的時間,則 cookie 會被刪除。
max-age=3600
?它是 expires
的替代選項,指明了 cookie 的過期時間距離當前時間的秒數(shù)。
如果將其設置為 0 或負數(shù),則 cookie 會被刪除:
// cookie 會在一小時后失效
document.cookie = "user=John; max-age=3600";
// 刪除 cookie(讓它立即過期)
document.cookie = "user=John; max-age=0";
secure
?Cookie 應只能被通過 HTTPS 傳輸。
默認情況下,如果我們在 http://site.com
上設置了 cookie,那么該 cookie 也會出現(xiàn)在 https://site.com
上,反之亦然。
也就是說,cookie 是基于域的,它們不區(qū)分協(xié)議。
使用此選項,如果一個 cookie 是通過 https://site.com
設置的,那么它不會在相同域的 HTTP 環(huán)境下出現(xiàn),例如 http://site.com
。所以,如果一個 cookie 包含絕不應該通過未加密的 HTTP 協(xié)議發(fā)送的敏感內(nèi)容,那么就應該設置 secure
標識。
// 假設我們現(xiàn)在在 HTTPS 環(huán)境下
// 設置 cookie secure(只在 HTTPS 環(huán)境下可訪問)
document.cookie = "user=John; secure";
這是另外一個關于安全的特性。它旨在防止 XSRF(跨網(wǎng)站請求偽造)攻擊。
為了了解它是如何工作的,以及何時有用,讓我們看一下 XSRF 攻擊。
想象一下,你登錄了 bank.com
網(wǎng)站。此時:你有了來自該網(wǎng)站的身份驗證 cookie。你的瀏覽器會在每次請求時將其發(fā)送到 bank.com
,以便識別你,并執(zhí)行所有敏感的財務上的操作。
現(xiàn)在,在另外一個窗口中瀏覽網(wǎng)頁時,你不小心訪問了另一個網(wǎng)站 evil.com
。該網(wǎng)站具有向 bank.com
網(wǎng)站提交一個具有啟動與黑客賬戶交易的字段的表單 <form action="https://bank.com/pay">
的 JavaScript 代碼。
你每次訪問 bank.com
時,瀏覽器都會發(fā)送 cookie,即使該表單是從 evil.com
提交過來的。因此,銀行會識別你的身份,并執(zhí)行真實的付款。
這就是所謂的“跨網(wǎng)站請求偽造(Cross-Site Request Forgery,簡稱 XSRF)”攻擊。
當然,實際的銀行會防止出現(xiàn)這種情況。所有由 bank.com
生成的表單都具有一個特殊的字段,即所謂的 “XSRF 保護 token”,惡意頁面既不能生成,也不能從遠程頁面提取它。它可以在那里提交表單,但是無法獲取數(shù)據(jù)。并且,網(wǎng)站 bank.com
會對收到的每個表單都進行這種 token 的檢查。
但是,實現(xiàn)這種防護需要花費時間。我們需要確保每個表單都具有所需的 token 字段,并且我們還必須檢查所有請求。
Cookie 的 samesite
選項提供了另一種防止此類攻擊的方式,(理論上)不需要要求 “XSRF 保護 token”。
它有兩個可能的值:
samesite=strict
?(和沒有值的 ?samesite
? 一樣)如果用戶來自同一網(wǎng)站之外,那么設置了 samesite=strict
的 cookie 永遠不會被發(fā)送。
換句話說,無論用戶是通過郵件鏈接還是從 evil.com
提交表單,或者進行了任何來自其他域下的操作,cookie 都不會被發(fā)送。
如果身份驗證 cookie 具有 samesite
選項,那么 XSRF 攻擊是沒有機會成功的,因為來自 evil.com
的提交沒有 cookie。因此,bank.com
將無法識別用戶,也就不會繼續(xù)進行付款。
這種保護是相當可靠的。只有來自 bank.com
的操作才會發(fā)送 samesite
cookie,例如來自 bank.com
的另一頁面的表單提交。
雖然,這樣有一些不方便。
當用戶通過合法的鏈接訪問 bank.com
時,例如從他們自己的筆記,他們會感到驚訝,bank.com
無法識別他們的身份。實際上,在這種情況下不會發(fā)送 samesite=strict
cookie。
我們可以通過使用兩個 cookie 來解決這個問題:一個 cookie 用于“一般識別”,僅用于說 “Hello, John”,另一個帶有 samesite=strict
的 cookie 用于進行數(shù)據(jù)更改的操作。這樣,從網(wǎng)站外部來的用戶會看到歡迎信息,但是支付操作必須是從銀行網(wǎng)站啟動的,這樣第二個 cookie 才能被發(fā)送。
samesite=lax
?一種更輕松的方法,該方法還可以防止 XSRF 攻擊,并且不會破壞用戶體驗。
寬松(lax)模式,和 strict
模式類似,當從外部來到網(wǎng)站,則禁止瀏覽器發(fā)送 cookie,但是增加了一個例外。
如果以下兩個條件均成立,則會發(fā)送含 samesite=lax
的 cookie:
所有安全的 HTTP 方法詳見 RFC7231 規(guī)范?;旧?,這些都是用于讀取而不是寫入數(shù)據(jù)的方法。它們不得執(zhí)行任何更改數(shù)據(jù)的操作。跟隨鏈接始終是 GET,是安全的方法。
這通常是成立的,但是如果導航是在一個 <iframe>
中執(zhí)行的,那么它就不是頂級的。此外,用于網(wǎng)絡請求的 JavaScript 方法不會執(zhí)行任何導航,因此它們不適合。
所以,samesite=lax
所做的是基本上允許最常見的“前往 URL”操作攜帶 cookie。例如,從筆記中打開網(wǎng)站鏈接就滿足這些條件。
但是,任何更復雜的事兒,例如來自另一個網(wǎng)站的網(wǎng)絡請求或表單提交都會丟失 cookie。
如果這種情況適合你,那么添加 samesite=lax
將不會破壞用戶體驗并且可以增加保護。
總體而言,samesite
是一個很好的選項。
但它有個缺點:
samesite
? 會被到 2017 年左右的舊版本瀏覽器忽略(不兼容)。因此,如果我們僅依靠 samesite
提供保護,那么在舊版本的瀏覽器上將很容易受到攻擊。
但是,我們肯定可以將 samesite
與其他保護措施一起使用,例如 XSRF token,這樣可以多增加一層保護,將來,當舊版本的瀏覽器淘汰時,我們可能就可以刪除 xsrf token 這種方式了。
這個選項和 JavaScript 沒有關系,但是我們必須為了完整性也提一下它。
Web 服務器使用 Set-Cookie
header 來設置 cookie。并且,它可以設置 httpOnly
選項。
這個選項禁止任何 JavaScript 訪問 cookie。我們使用 document.cookie
看不到此類 cookie,也無法對此類 cookie 進行操作。
這是一種預防措施,當黑客將自己的 JavaScript 代碼注入網(wǎng)頁,并等待用戶訪問該頁面時發(fā)起攻擊,而這個選項可以防止此時的這種攻擊。這應該是不可能發(fā)生的,黑客應該無法將他們的代碼注入我們的網(wǎng)站,但是網(wǎng)站有可能存在 bug,使得黑客能夠?qū)崿F(xiàn)這樣的操作。
通常來說,如果發(fā)生了這種情況,并且用戶訪問了帶有黑客 JavaScript 代碼的頁面,黑客代碼將執(zhí)行并通過 document.cookie
獲取到包含用戶身份驗證信息的 cookie。這就很糟糕了。
但是,如果 cookie 設置了 httpOnly
,那么 document.cookie
則看不到 cookie,所以它受到了保護。
這里有一組有關 cookie 操作的函數(shù),比手動修改 document.cookie
方便得多。
有很多這種 cookie 庫,所以這些函數(shù)只用于演示。雖然它們都能正常使用。
獲取 cookie 最簡短的方式是使用 正則表達式。
getCookie(name)
函數(shù)返回具有給定 name
的 cookie:
// 返回具有給定 name 的 cookie,
// 如果沒找到,則返回 undefined
function getCookie(name) {
let matches = document.cookie.match(new RegExp(
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
這里的 new RegExp
是動態(tài)生成的,以匹配 ; name=<value>
。
請注意 cookie 的值是經(jīng)過編碼的,所以 getCookie
使用了內(nèi)建方法 decodeURIComponent
函數(shù)對其進行解碼。
將 cookie 的 name
設置為具有默認值 path=/
(可以修改以添加其他默認值)和給定值 value
:
function setCookie(name, value, options = {}) {
options = {
path: '/',
// 如果需要,可以在這里添加其他默認值
...options
};
if (options.expires instanceof Date) {
options.expires = options.expires.toUTCString();
}
let updatedCookie = encodeURIComponent(name) + "=" + encodeURIComponent(value);
for (let optionKey in options) {
updatedCookie += "; " + optionKey;
let optionValue = options[optionKey];
if (optionValue !== true) {
updatedCookie += "=" + optionValue;
}
}
document.cookie = updatedCookie;
}
// 使用范例:
setCookie('user', 'John', {secure: true, 'max-age': 3600});
要刪除一個 cookie,我們可以給它設置一個負的過期時間來調(diào)用它:
function deleteCookie(name) {
setCookie(name, "", {
'max-age': -1
})
}
更新或刪除必須使用相同的路徑和域
請注意:當我們更新或刪除一個 cookie 時,我們應該使用和設置 cookie 時相同的路徑和域選項。
代碼放在:cookie.js。
如果 cookie 是由用戶所訪問的頁面的域以外的域放置的,則稱其為第三方 cookie。
例如:
site.com
? 網(wǎng)站的一個頁面加載了另外一個網(wǎng)站的 banner:?<img src="https://ads.com/banner.png" rel="external nofollow" >
?。ads.com
? 的遠程服務器可能會設置帶有 ?id=1234
? 這樣的 cookie 的 ?Set-Cookie
? header。此類 cookie 源自 ?ads.com
? 域,并且僅在 ?ads.com
? 中可見:
ads.com
網(wǎng)站時,遠程服務器獲取 cookie id
并識別用戶:
site.com
網(wǎng)站跳轉(zhuǎn)至另一個也帶有 banner 的網(wǎng)站 other.com
時,ads.com
會獲得該 cookie,因為它屬于 ads.com
,從而識別用戶并在他在網(wǎng)站之間切換時對其進行跟蹤:
由于它的性質(zhì),第三方 cookie 通常用于跟蹤和廣告服務。它們被綁定在原始域上,因此 ads.com
可以在不同網(wǎng)站之間跟蹤同一用戶,如果這些網(wǎng)站都可以訪問 ads.com
的話。
當然,有些人不喜歡被跟蹤,因此瀏覽器允許禁止此類 cookie。
此外,一些現(xiàn)代瀏覽器對此類 cookie 采取特殊策略:
請注意:
如果我們加載了一個來自第三方域的腳本,例如
<script src="https://google-analytics.com/analytics.js" rel="external nofollow" >
,并且該腳本使用document.cookie
設置了 cookie,那么此類 cookie 就不是第三方的。
如果一個腳本設置了一個 cookie,那么無論腳本來自何處 —— 這個 cookie 都屬于當前網(wǎng)頁的域。
本主題和 JavaScript 無關,只是設置 cookie 時的一些注意事項。
歐洲有一項名為 GDPR 的立法,該法規(guī)針對網(wǎng)站尊重用戶實施了一系列規(guī)則。其中之一就是需要明確的許可才可以跟蹤用戶的 cookie。
請注意,這僅與跟蹤/識別/授權(quán) cookie 有關。
所以,如果我們設置一個只保存了一些信息的 cookie,但是既不跟蹤也不識別用戶,那么我們可以自由地設置它。
但是,如果我們要設置帶有身份驗證會話(session)或跟蹤 id 的 cookie,那么必須得到用戶的允許。
網(wǎng)站為了遵循 GDPR 通常有兩種做法。你一定已經(jīng)在網(wǎng)站中看到過它們了:
為此,注冊表單中必須要有一個復選框,例如“接受隱私政策”(描述怎么使用 cookie),用戶必須勾選它,然后網(wǎng)站就可以自由設置身份驗證 cookie 了。
為了合法地這樣做,網(wǎng)站為每個新用戶顯示一個“初始屏幕”彈窗,并要求他們同意設置 cookie。之后網(wǎng)站就可以設置 cookie,并可以讓用戶看到網(wǎng)站內(nèi)容了。不過,這可能會使新用戶感到反感。沒有人喜歡看到“必須點擊”的初始屏幕彈窗而不是網(wǎng)站內(nèi)容。但是 GDPR 要求必須得到用戶明確地準許。
GDPR 不僅涉及 cookie,還涉及其他與隱私相關的問題,但這超出了我們的討論范圍。
document.cookie
提供了對 cookie 的訪問
Cookie 選項:
path=/
?,默認為當前路徑,使 cookie 僅在該路徑下可見。domain=site.com
?,默認 cookie 僅在當前域下可見。如果顯式地設置了域,可以使 cookie 在子域下也可見。expires
? 或 ?max-age
? 設定了 cookie 過期時間。如果沒有設置,則當瀏覽器關閉時 cookie 就會失效。secure
? 使 cookie 僅在 HTTPS 下有效。samesite
?,如果請求來自外部網(wǎng)站,禁止瀏覽器發(fā)送 cookie。這有助于防止 XSRF 攻擊。另外:
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: