「必需知道」實用,完整的HTTP cookie指南

2020-09-03 前端小智

Web 開發中的 cookie 是什麼?

cookie 是後端可以存儲在用戶瀏覽器中的小塊數據。 Cookie 最常見用例包括用戶跟蹤,個性化以及身份驗證。

Cookies 具有很多隱私問題,多年來一直受到嚴格的監管。

在本文中,主要側重於技術方面:學習如何在前端和後端創建,使用 HTTP cookie。

後端配置

後端示例是Flask編寫的。如果你想跟著學習,可以創建一個新的Python虛擬環境,移動到其中並安裝Flask

mkdir cookies && cd $_python3 -m venv venvsource venv/bin/activatepip install Flask

在項目文件夾中創建一個名為flask app.py的新文件,並使用本文的示例在本地進行實驗。

誰創建 cookies ?

首先,cookies 從何而來?誰創建 cookies ?

雖然可以使用document.cookie在瀏覽器中創建 cookie,但大多數情況下,後端的責任是在將響應客戶端請求之前在請求中設置 cookie。

後端是指可以通過以下方式創建 Cookie:

  • 後端實際應用程式的代碼(Python、JavaScript、PHP、Java)
  • 響應請求的Web伺服器(Nginx,Apache)

後端可以在 HTTP 請求求中 Set-Cookie 屬性來設置 cookie,它是由鍵/值對以及可選屬性組成的相應字符串:

Set-Cookie: myfirstcookie=somecookievalue

什麼時候需要創建 cookie?這取決於需求。

cookie 是簡單的字符串。在項目文件夾中創建一個名為flask_app.py的Python文件,並輸入以下內容:

from flask import Flask, make_responseapp = Flask(__name__)@app.route(&34;, methods=[&34;])def index():    response = make_response(&34;)    response.headers[&34;] = &34;    return response

然後運行應用程式:

FLASK_ENV=development FLASK_APP=flask_app.py flask run

當該應用程式運行時,用戶訪問http://127.0.0.1:5000/index/,後端將設置一個具有鍵/值對的名為Set-Cookie的響應標頭。

(127.0.0.1:5000是開發中的 Flask 應用程式的默認偵聽地址/埠)。

Set-Cookie標頭是了解如何創建cookie的關鍵:

response.headers[&34;] = &34;

大多數框架都有自己設置 cookie 的方法,比如Flask的set_cookie()。

如何查看 cookies ?

訪問http://127.0.0.1:5000/index/後,後端將在瀏覽器中設置cookie。要查看此cookie,可以從瀏覽器的控制臺調用document.cookie:

或者可以在開發人員工具中選中Storage選項卡。單擊cookie,會看到 cookie 具體的內容:

在命令行上,還可以使用curl查看後端設置了哪些 cookie

curl -I http://127.0.0.1:5000/index/

可以將 Cookie 保存到文件中以供以後使用:

curl -I http://127.0.0.1:5000/index/ --cookie-jar mycookies

在 stdout 上顯示 cookie:

curl -I http://127.0.0.1:5000/index/ --cookie-jar -

請注意,沒有HttpOnly屬性的cookie,在瀏覽器中可以使用document.cookie上訪問,如果設置了 HttpOnly 屬性,document.cookie就讀取不到。

Set-Cookie: myfirstcookie=somecookievalue; HttpOnly

現在,該cookie 仍將出現在 Storage 選項卡中,但是 document.cookie返回的是一個空字符串。

從現在開始,為方便起見,使用Flask的 response.set_cookie() 在後端上創建 cookie。

我有一個 cookie,現在怎麼辦?

你的瀏覽器得到一個 cookie。現在怎麼辦呢?一旦有了 cookie,瀏覽器就可以將cookie發送回後端。

這有許多用途發如:用戶跟蹤、個性化,以及最重要的身份驗證。

例如,一旦你登錄網站,後端就會給你一個cookie:

Set-Cookie: userid=sup3r4n0m-us3r-1d3nt1f13r

為了在每個後續請求中正確識別 我們的身份,後端會檢查來自請求中瀏覽器的 cookie

要發送Cookie,瀏覽器會在請求中附加一個Cookie標頭:

Cookie: userid=sup3r4n0m-us3r-1d3nt1f13r

cookie 可以設置過期時間: Max-Age 和 expires

默認情況下,cookie 在用戶關閉會話時即關閉瀏覽器時過期。要持久化cookie,我們可以通過expires或Max-Age屬性

Set-Cookie: myfirstcookie=somecookievalue; expires=Tue, 09 Jun 2020 15:46:52 GMT; Max-Age=1209600

注意:Max-Age優先於expires

cookie的作用域是網站路徑: path 屬性

考慮該後端,該後端在訪問http://127.0.0.1:5000/時為其前端設置了一個新的 cookie。相反,在其他兩條路徑上,我們列印請求的cookie:

from flask import Flask, make_response, requestapp = Flask(__name__)@app.route(&34;, methods=[&34;])def index():    response = make_response(&34;)    response.set_cookie(key=&34;, value=&34;, path=&34;)    return response@app.route(&34;, methods=[&34;])def about():    print(request.cookies)    return &34;@app.route(&34;, methods=[&34;])def contact():    print(request.cookies)    return &34;

運行該應用程式:

FLASK_ENV=development FLASK_APP=flask_app.py flask run

在另一個終端中,如果我們與根路由建立連接,則可以在Set-Cookie中看到cookie:

curl -I http://127.0.0.1:5000/ --cookie-jar cookiesHTTP/1.0 200 OKContent-Type: text/html; charset=utf-8Content-Length: 23Set-Cookie: id=3db4adj3d; Path=/about/Server: Werkzeug/1.0.1 Python/3.8.3Date: Wed, 27 May 2020 09:21:37 GMT

請注意,此時 cookie 具有Path屬性:

Set-Cookie: id=3db4adj3d; Path=/about/

/about/ 路由並保存 cookit

curl -I http://127.0.0.1:5000/about/ --cookie cookies

在 Flask 應用程式的終端中運行如下命令,可以看到:

ImmutableMultiDict([(&39;, &39;)])127.0.0.1 - - [27/May/2020 11:27:55] &34; 200 -

正如預期的那樣,cookie 返回到後端。現在嘗試訪問 /contact/ 路由:

url -I http://127.0.0.1:5000/contact/ --cookie cookies

在 Flask 應用程式的終端中運行如下命令,可以看到:

ImmutableMultiDict([])127.0.0.1 - - [27/May/2020 11:29:00] &34; 200 -

這說明啥?cookie 的作用域是Path 。具有給定路徑屬性的cookie不能被發送到另一個不相關的路徑,即使這兩個路徑位於同一域中。

這是cookie權限的第一層。

在cookie創建過程中省略Path時,瀏覽器默認為/。

cookie 的作用域是域名: domain 屬性

cookie 的 Domain 屬性的值控制瀏覽器是否應該接受cookie以及cookie返回的位置。

讓我們看一些例子。

主機不匹配(錯誤的主機)

查看 https://serene-bastion-01422.herokuapp.com/get-wrong-domain-cookie/設置的cookie:

Set-Cookie: coookiename=wr0ng-d0m41n-c00k13; Domain=api.valentinog.com

這裡的 cookie 來自serene-bastion-01422.herokuapp.com,但是Domain屬性具有api.valentinog.com

瀏覽器沒有其他選擇來拒絕這個 cookie。比如 Chrome 會給出一個警告(Firefox沒有)

主機不匹配(子域名)

查看 https://serene-bastion-01422.herokuapp.com/get-wrong-subdomain-cookie/設置的cookie:

Set-Cookie: coookiename=wr0ng-subd0m41n-c00k13; Domain=secure-brushlands-44802.herokuapp.com

這裡的 Cookie 來自serene-bastion-01422.herokuapp.com,但**「Domain」**屬性是secure-brushlands-44802.herokuapp.com。

它們在相同的域上,但是子域名不同。同樣,瀏覽器也拒絕此cookie:

主機匹配(整個域)

查看 https://www.valentinog.com/get-domain-cookie.html設置的cookie:

set-cookie: cookiename=d0m41n-c00k13; Domain=valentinog.com

此cookie是使用 Nginx add_header在Web伺服器上設置的:

add_header Set-Cookie &34;;

這裡使用 Nginx 中設置cookie的多種方法。Cookie 是由 Web 伺服器或應用程式的代碼設置的,對於瀏覽器來說無關緊要。

重要的是 cookie 來自哪個域。

在此瀏覽器將愉快地接受cookie,因為Domain中的主機包括cookie所來自的主機。

換句話說,valentinog.com包括子域名www.valentinog.com。

同時,對valentinog.com的新請求,cookie 都會攜帶著,以及任何對valentinog.com子域名的請求。

這是一個附加了Cookie的 www 子域請求:

下面是對另一個自動附加cookie的子域的請求

Cookies 和公共後綴列表

查看 https://serene-bastion-01422.herokuapp.com/get-domain-cookie/:設置的 cookie:

Set-Cookie: coookiename=d0m41n-c00k13; Domain=herokuapp.com

這裡的 cookie 來自serene-bas-01422.herokuapp.com,Domain 屬性是herokuapp.com。瀏覽器在這裡應該做什麼

你可能認為serene-base-01422.herokuapp.com包含在herokuapp.com域中,因此瀏覽器應該接受cookie。

相反,它拒絕 cookie,因為它來自公共後綴列表中包含的域。

Public Suffix List(公共後綴列表)。此列表列舉了頂級域名和開放註冊的域名。瀏覽器禁止此列表上的域名被子域名寫入Cookie。

主機匹配(子域)

查看 https://serene-bastion-01422.herokuapp.com/get-subdomain-cookie/:設置的 cookie:

Set-Cookie: coookiename=subd0m41n-c00k13

當域在cookie創建期間被省略時,瀏覽器會默認在地址欄中顯示原始主機,在這種情況下,我的代碼會這樣做:

response.set_cookie(key=&34;, value=&34;)

當 Cookie 進入瀏覽器的 Cookie 存儲區時,我們看到已應用Domain :

現在,我們有來自serene-bastion-01422.herokuapp.com 的 cookie, 那 cookie 現在應該送到哪裡?

如果你訪問https://serene-bastion-01422.herokuapp.com/,則 cookie 隨請求一起出現:

但是,如果訪問herokuapp.com,則 cookie 不會隨請求一起出現:

概括地說,瀏覽器使用以下啟發式規則來決定如何處理cookies(這裡的發送者主機指的是你訪問的實際網址):

  • 如果「Domain」中的域或子域與訪問的主機不匹配,則完全拒絕 Cookie
  • 如果 Domain 的值包含在公共後綴列表中,則拒絕 cookie
  • 如果Domain 中的域或子域與訪問在主機匹配,則接受 Cookie

一旦瀏覽器接受了cookie,並且即將發出請求,它就會說:

  • 如果請求主機與我在Domain中看到的值完全匹配,則會回傳 cookie
  • 如果請求主機是與我在「Domain」中看到的值完全匹配的子域,則將回傳 cookie
  • 如果請求主機是sub.example.dev之類的子域,包含在example.dev之類的 Domain 中,則將回傳 cookie
  • 如果請求主機是例如example.dev之類的主域,而 Domain 是sub.example.dev之類,則不會回傳cookie。

Domain 和 Path 屬性一直是 cookie 權限的第二層。

Cookies可以通過AJAX請求傳遞

Cookies 可以通過AJAX請求傳播。AJAX 請求是使用 JS (XMLHttpRequest或Fetch)進行的異步HTTP請求,用於獲取數據並將其發送回後端。

考慮 Flask的另一個示例,其中有一個模板,該模板又會加載 JS 文件:

from flask import Flask, make_response, render_templateapp = Flask(__name__)@app.route(&34;, methods=[&34;])def index():    return render_template(&34;)@app.route(&34;, methods=[&34;])def get_cookie():    response = make_response(&34;)    response.set_cookie(key=&34;, value=&34;)    return response

以下是 templates/index.html 模板:

<!DOCTYPE html><html lang=&34;><head>    <meta charset=&34;>    <title>Title</title></head><body><button>FETCH</button></body><script src=&39;static&39;index.js&34;></script></html>

下面是 static/index.js 的內容:

const button = document.getElementsByTagName(&34;)[0];button.addEventListener(&34;, function() {  getACookie();});function getACookie() {  fetch(&34;)    .then(response => {      // make sure to check response.ok in the real world!      return response.text();    })    .then(text => console.log(text));}

當訪問http://127.0.0.1:5000/時,我們會看到一個按鈕。通過單擊按鈕,我們向/get-cookie/發出獲取請求並獲取Cookie。正如預期的那樣,cookie 落在瀏覽器的 Cookie storage中。

對 Flask 應用程式進行一些更改,多加一個路由:

from flask import Flask, make_response, request, render_template, jsonifyapp = Flask(__name__)@app.route(&34;, methods=[&34;])def index():    return render_template(&34;)@app.route(&34;, methods=[&34;])def get_cookie():    response = make_response(&34;)    response.set_cookie(key=&34;, value=&34;)    return response@app.route(&34;, methods=[&34;])def cities():    if request.cookies[&34;] == &34;:        cities = [{&34;: &34;, &34;: 1}, {&34;: &34;, &34;: 2}]        return jsonify(cities)    return jsonify(msg=&34;)

另外,調整一下 JS 代碼,用於下請求剛新增的路由:

const button = document.getElementsByTagName(&34;)[0];button.addEventListener(&34;, function() {  getACookie().then(() => getData());});function getACookie() {  return fetch(&34;).then(response => {    // make sure to check response.ok in the real world!    return Promise.resolve(&34;);  });}function getData() {  fetch(&34;)    .then(response => {      // make sure to check response.ok in the real world!      return response.json();    })    .then(json => console.log(json));

當訪問http://127.0.0.1:5000/時,我們會看到一個按鈕。通過單擊按鈕,我們向/get-cookie/發出獲取請求以獲取Cookie。Cookie出現後,我們就會對/api/cities/再次發出Fetch請求。

在瀏覽器的控制臺中,可以看到請求回來 的數據。另外,在開發者工具的Network選項卡中,可以看到一個名為Cookie的頭,這是通過AJAX請求傳給後端。

只要前端與後端在同一上下文中,在前端和後端之間來回交換cookie就可以正常工作:我們說它們來自同源。

這是因為默認情況下,Fetch 僅在請求到達觸發請求的來源時才發送憑據,即 Cookie。

cookie 不能總是通過AJAX請求傳遞

考慮另一種情況,在後端獨立運行,可以這樣啟動應用程式:

FLASK_ENV=development FLASK_APP=flask_app.py flask run

現在,在 Flask 應用程式之外的其他文件夾中,創建index.html:

<!DOCTYPE html><html lang=&34;><head>    <meta charset=&34;>    <title>Title</title></head><body><button>FETCH</button></body><script src=&34;></script></html>

使用以下代碼在同一文件夾中創建一個名為index.js的 JS 文件:

button.addEventListener(&34;, function() {  getACookie().then(() => getData());});function getACookie() {  return fetch(&34;).then(response => {    // make sure to check response.ok in the real world!    return Promise.resolve(&34;);  });}function getData() {  fetch(&34;)    .then(response => {      // make sure to check response.ok in the real world!      return response.json();    })    .then(json => console.log(json));}

在同一文件夾中,從終端運行:

npx serve

此命令為您提供了要連接的本地地址/埠,例如http://localhost:42091/。訪問頁面並嘗試在瀏覽器控制臺打開的情況下單擊按鈕。在控制臺中,可以看到:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/get-cookie/. (Reason: CORS header 『Access-Control-Allow-Origin』 missing)

因為 http://localhost:5000/ 與http://localhost:42091/.不同。它們是不同的域,因此會 CORS 的限制。

處理 CORS

CORS 是一個 W3C 標準,全稱是「跨域資源共享」(Cross-origin resource sharing)。它允許瀏覽器向跨域的伺服器,發出XMLHttpRequest請求,從而克服了 AJAX 只能同源使用的限制。

整個 CORS 通信過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS 通信與普通的 AJAX 通信沒有差別,代碼完全一樣。瀏覽器一旦發現 AJAX 請求跨域,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感知。因此,實現 CORS 通信的關鍵是伺服器。只要伺服器實現了 CORS 接口,就可以跨域通信。

默認情況下,除非伺服器設置了Access-Control-Allow-Origin的特定HTTP標頭,否則瀏覽器將阻止AJAX對非相同來源的遠程資源的請求。

要解決此第一個錯誤,我們需要為Flask配置CORS:

pip install flask-cors

然後將 CORS 應用於 Flask:

from flask import Flask, make_response, request, render_template, jsonifyfrom flask_cors import CORSapp = Flask(__name__)CORS(app=app)@app.route(&34;, methods=[&34;])def index():    return render_template(&34;)@app.route(&34;, methods=[&34;])def get_cookie():    response = make_response(&34;)    response.set_cookie(key=&34;, value=&34;)    return response@app.route(&34;, methods=[&34;])def cities():    if request.cookies[&34;] == &34;:        cities = [{&34;: &34;, &34;: 1}, {&34;: &34;, &34;: 2}]        return jsonify(cities)    return jsonify(msg=&34;)

現在嘗試在瀏覽器控制臺打開的情況下再次單擊按鈕。在控制臺中你應該看到

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/api/cities/. (Reason: CORS header 『Access-Control-Allow-Origin』 missing)

儘管我們犯了同樣的錯誤,但這次的罪魁禍首是第二個路由。

你可以通過查看 「Network」 標籤中的請求來確認,沒有發送此類Cookie:

為了在不同來源的Fetch請求中包含cookie,我們必須提credentials 標誌(默認情況下,它是相同來源)。

如果沒有這個標誌,Fetch 就會忽略 cookie,可以這樣修復:

const button = document.getElementsByTagName(&34;)[0];button.addEventListener(&34;, function() {  getACookie().then(() => getData());});function getACookie() {  return fetch(&34;, {    credentials: &34;  }).then(response => {    // make sure to check response.ok in the real world!    return Promise.resolve(&34;);  });}function getData() {  fetch(&34;, {    credentials: &34;  })    .then(response => {      // make sure to check response.ok in the real world!      return response.json();    })    .then(json => console.log(json));}

credentials: &34; 必須在第一個 Fetch 請求中出現,才能將Cookie保存在瀏覽器的Cookie storage 中:

fetch(&34;, {    credentials: &34;  })

它還必須在第二個請求時出現,以允許將cookie傳輸回後端

  fetch(&34;, {    credentials: &34;  })

再試一次,我們還需要在後端修復另一個錯誤:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/get-cookie/. (Reason: expected 『true』 in CORS header 『Access-Control-Allow-Credentials』).

為了允許在CORS請求中傳輸cookie,後端還需要設置 Access-Control-Allow-Credentials標頭。

CORS(app=app, supports_credentials=True)

要點:為了使Cookie在不同來源之間通過AJAX請求傳遞,可以這樣做:

  • credentials: &34; 用於前端的 fetch 請求中
  • Access-Control-Allow-Credentials 和 Access-Control-Allow-Origin 用於後端

cookie可以通過AJAX請求傳遞,但是它們必須遵守我們前面描述的域規則。

Cookie 的 Secure 屬性

Secure 屬性是說如果一個 cookie 被設置了Secure=true,那麼這個cookie只能用https協議發送給伺服器,用 http 協議是不發送的。換句話說,cookie 是在https的情況下創建的,而且他的Secure=true,那麼之後你一直用https訪問其他的頁面(比如登錄之後點擊其他子頁面),cookie會被發送到伺服器,你無需重新登錄就可以跳轉到其他頁面。但是如果這時你把url改成http協議訪問其他頁面,你就需要重新登錄了,因為這個cookie不能在http協議中發送。

可以這樣設置 Secure 屬性

response.set_cookie(key=&34;, value=&34;, secure=True)

如果要在真實環境中嘗試,請可以運行以下命令,並注意curl在此處是不通過HTTP保存cookie:

curl -I http://serene-bastion-01422.herokuapp.com/get-secure-cookie/ --cookie-jar -

相反,通過HTTPS,cookie 出現在cookie jar中:

curl -I https://serene-bastion-01422.herokuapp.com/get-secure-cookie/ --cookie-jar -

cookie jar 文件:

serene-bastion-01422.herokuapp.com      FALSE   /       TRUE    0

不要被Secure欺騙:瀏覽器通過HTTPS接受cookie,但是一旦cookie進入瀏覽器,就沒有任何保護。

因為帶有 Secure 的 Cookie 一般也不用於傳輸敏感數據.

Cookie 的 HttpOnly 屬性

如果cookie中設置了HttpOnly屬性,那麼通過js腳本將無法讀取到cookie信息,這樣能有效的防止XSS攻擊,竊取cookie內容,這樣就增加了cookie的安全性,即便是這樣,也不要將重要信息存入cookie。

XSS 全稱Cross SiteScript,跨站腳本攻擊,是Web程序中常見的漏洞,XSS屬於被動式且用於客戶端的攻擊方式,所以容易被忽略其危害性。其原理是攻擊者向有XSS漏洞的網站中輸入(傳入)惡意的HTML代碼,當其它用戶瀏覽該網站時,這段HTML代碼會自動執行,從而達到攻擊的目的。如,盜取用戶Cookie、破壞頁面結構、重定向到其它網站等。

如果有設置 HttpOnly 看起來是這樣的:

Set-Cookie: &34;

在 Flask 中

response.set_cookie(key=&34;, value=&34;, httponly=True)

這樣,cookie 設置了HttpOnly屬性,那麼通過js腳本將無法讀取到cookie信息。如果在控制臺中進行檢查,則document.cookie將返回一個空字符串。

何時使用HttpOnly?cookie 應該始終是HttpOnly的,除非有特定的要求將它們暴露給運行時 JS。

可怕的 SameSite 屬性

first-party cookie 和 third-party cookie

查看https://serene-bastion-01422.herokuapp.com/get-cookie/ 中所攜帶的 Cookie

Set-Cookie: simplecookiename=c00l-c00k13; Path=/

first-party是指你登錄或使用的網站所發行的 cookie,而third-party cookie 常為一些廣告網站,有侵犯隱私以及安全隱患。

我們將這類 Cookie 稱為 first-party。也就是說,我在瀏覽器中訪問該URL,並且如果我訪問相同的URL或該站點的另一個路徑(假設Path為/),則瀏覽器會將cookie發送回該網站。

現在考慮在https://serene-bastion-01422.herokuapp.com/get-frog/上的另一個網頁。該頁面設置了一個cookie,此外,它還從https://www.valentinog.com/cookie-frog.jpg託管的遠程資源中加載圖像。

該遠程資源又會自行設置一個cookie:

我們將這種 cookie 稱為third-party(第三方) Cookie。

第三方 Cookie 除了用於 CSRF 攻擊,還可以用於用戶追蹤。比如,Facebook 在第三方網站插入一張看不見的圖片。

<img src=&34; style=&34;>

瀏覽器加載上面代碼時,就會向 Facebook 發出帶有 Cookie 的請求,從而 Facebook 就會知道你是誰,訪問了什麼網站。

使用 SameSite 屬性

Cookie 的SameSite 屬性用來限制third-party Cookie,從而減少安全風險。它可以設置三個值。

  • Strict
  • Lax
  • None

Strict最為嚴格,完全禁止第三方 Cookie,跨站點時,任何情況下都不會發送 Cookie。換言之,只有當前網頁的 URL 與請求目標一致,才會帶上 Cookie。

Set-Cookie: CookieName=CookieValue; SameSite=Strict;

這個規則過於嚴格,可能造成非常不好的用戶體驗。比如,當前網頁有一個 GitHub 連結,用戶點擊跳轉就不會帶有 GitHub 的 Cookie,跳轉過去總是未登陸狀態。

Lax規則稍稍放寬,大多數情況也是不發送第三方 Cookie,但是導航到目標網址的 Get 請求除外。

Set-Cookie: CookieName=CookieValue; SameSite=Lax;

導航到目標網址的 GET 請求,只包括三種情況:連結,預加載請求,GET 表單。詳見下表。

設置了Strict或Lax以後,基本就杜絕了 CSRF 攻擊。當然,前提是用戶瀏覽器支持 SameSite 屬性。

Chrome 計劃將Lax變為默認設置。這時,網站可以選擇顯式關閉SameSite屬性,將其設為None。不過,前提是必須同時設置Secure屬性(Cookie 只能通過 HTTPS 協議發送),否則無效。

下面的設置無效。

Set-Cookie: widget_session=abc123; SameSite=None

下面的設置有效。

Set-Cookie: widget_session=abc123; SameSite=None; Secure

Cookies 和 認證

身份驗證是 web 開發中最具挑戰性的任務之一。關於這個主題似乎有很多困惑,因為JWT中的基於令牌的身份驗證似乎要取代「舊的」、可靠的模式,如基於會話的身份驗證。

來看看 cookie 在這裡扮演什麼角色。

基於會話的身份驗證

身份驗證是 cookie 最常見的用例之一。

當你訪問一個請求身份驗證的網站時,後端將通過憑據提交(例如通過表單)在後臺發送一個Set-Cookie標頭到前端。

型的會話 cookie 如下所示:

Set-Cookie: sessionid=sty1z3kz11mpqxjv648mqwlx4ginpt6c; expires=Tue, 09 Jun 2020 15:46:52 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax

這個Set-Cookie頭中,伺服器可以包括一個名為session、session id或類似的cookie。

這是瀏覽器可以清楚看到的唯一標識符。每當通過身份驗證的用戶向後端請求新頁面時,瀏覽器就會發回會話cookie。

基於會話的身份驗證是有狀態的,因為後端必須跟蹤每個用戶的會話。這些會話的存儲可能是:

  • 資料庫
  • 像 Redis 這樣的鍵/值存儲
  • 文件系統

在這三個會話存儲中,Redis 之類應優先於資料庫或文件系統。

請注意,基於會話的身份驗證與瀏覽器的會話存儲無關。

之所以稱為基於會話的會話,是因為用於用戶識別的相關數據存在於後端的會話存儲中,這與瀏覽器的會話存儲不同。

何時使用基於會話的身份驗證

只要能使用就使用它。基於會話的身份驗證是一種最簡單、安全、直接的網站身份驗證形式。默認情況下,它可以在Django等所有流行的web框架上使用。

但是,它的狀態特性也是它的主要缺點,特別是當網站是由負載均衡器提供服務時。在這種情況下,像粘貼會話,或者在集中的Redis存儲上存儲會話這樣的技術會有所幫助。

關於 JWT 的說明

JWT是 JSON Web Tokens的縮寫,是一種身份驗證機制,近年來越來越流行。

JWT 非常適合單頁和行動應用程式,但它帶來了一系列新挑戰。想要針對API進行身份驗證的前端應用程式的典型流程如下:

  • 前端將憑證發送到後端
  • 後端檢查憑證並發回令牌
  • 前端在每個後續請求上帶上該令牌

這種方法帶來的主要問題是:為了使用戶保持登錄狀態,我將該令牌存儲在前端的哪個地方?

對於前端開發來說,最自然的事情是將令牌保存在localStorage中。由於許多原因,這很糟糕。

localStorage很容易從 JS 代碼訪問,而且它很容易成為XSS攻擊的目標。

為了解決此問題,大多數開發人員都將JWT令牌保存在cookie中,以為HttpOnly和Secure可以保護cookie,至少可以免受XSS攻擊。

將 SameSite 設置為 strict 就可以完全保護 JWT免受CSRF攻擊

設置為SameSite = Strict的新SameSite屬性還將保護你的「熟化」 JWT免受CSRF攻擊。但是,由於SameSite = Strict不會在跨域請求上發送cookie,因此,這也完全使JWT的用例無效。

那SameSite=Lax呢?此模式允許使用安全的HTTP方法(即GET,HEAD,OPTIONS和TRACE)將 cookie發送回去。POST 請求不會以任何一種方式傳輸 cookie。

實際上,將JWT標記存儲在cookie或localStorage中都不是好主意。

如果你確實要使用JWT而不是堅持使用基於會話的身份驗證並擴展會話存儲,則可能要使用帶有刷新令牌的JWT來保持用戶登錄。

總結

自1994年以來,HTTP cookie一直存在,它們無處不在。

Cookies是簡單的文本字符串,但可以通過Domain和Path對其權限進行控制,具有Secure的Cookie,只能通過 HTTP S進行傳輸,而可以使用 HttpOnly從 JS隱藏。

但是,對於所有預期的用途,cookie都可能使用戶暴露於攻擊和漏洞之中。

瀏覽器的供應商和Internet工程任務組(Internet Engineering Task Force)年復一年地致力於提高cookie的安全性,最近的一步是SameSite。

那麼,什麼才算是比較安全cookie?,如下幾點:

  • 僅使用 HTTPS
  • 儘可能帶有 HttpOnly 屬性
  • 正確的SameSite配置
  • 不攜帶敏感數據

人才們的 【三連】 就是小智不斷分享的最大動力,如果本篇博客有任何錯誤和建議,歡迎人才們留言,最後,謝謝大家的觀看。


作者:valentinog 譯者:前端小智 來源:valentinog

原文:https://gizmodo.com/the-complete-guide-to-cookies-and-all-the-stuff-w-1794247382

相關焦點

  • 「Find」php - 實現含cookie的POST請求
    「公告」文章前綴「xxx」的含義大家好,我是Soft98,今天給大家帶來的是php - 實現含cookie的POST請求,因為也是借鑑的網上的方法,所以這裡歸類為Find系列。廢話不多說,直接步入正題:<?
  • 為什麼及如何清除 iPhone 手機中的 Cookie 與緩存
    你可能已經聽說過涉及 iPhone 和計算機的 cookie,但是你知道這些術語是什麼意思嗎?或如何清除Cookie 以及為什麼它很重要?彈出「清除歷史記錄和數據」時,點擊以確認。這不會刪除存儲在自動填充中的信息。
  • node中的cookie作用和用法
    我們知道http是無狀態的協議,無狀態是什麼意思呢?我來舉一個小例子來說明:比如小明在網上購物,他瀏覽了多個頁面,購買了一些物品,這些請求在多次連接中完成,如果不藉助額外的手段,那麼伺服器是不知道他到底購買了什麼的,因為伺服器壓根就不知道每次請求的到底是不是小明,除非小明有一個標識來證明他是小明。所以,網站為了辨別用戶身份,進行 session 跟蹤,cookie出現了。cookie簡單來說,cookie就是標識。
  • Java Web 應用 Cookie 安全指南
    背景Java Web 應用中,如果沒有對 JESSIONID 這類 Cookie 信息設置 httpOnly 屬性,就存在這種風險:可以通過 js 的 document.cookie 列印會話信息,並竊取或操縱客戶會話和 cookie,它們可能用於模仿合法用戶,從而使黑客能夠以該用戶身份查
  • 「大川的指南」2020年初Apple Watch選購指南
    在選購指南之前,首先希望各位再次明確「我真的需要一塊Apple Watch嗎?」我想把這個問題拆分為兩個方面來解答。優點:1⃣️目前來說體驗最完整2⃣️息屏顯示提高效率 息屏顯示在非抬腕情況下也保持屏幕顯示內容,讓原本「抬腕-查看內容」變成瞟一眼手錶就能直接觀看,在使用的邏輯上還是簡化了一步的。
  • 「入坑指南 6」《如龍》系列
    今天給大家帶來的是《入坑指南》的第6期,《如龍》系列(官方中文譯作《人中之龍》系列)。由於該系列比較長,圖片分成了兩頁,大家請不要忘掉還有第2頁。有什麼意見建議還請大家踴躍留言,謝謝~《如龍》系列最新的「正傳」作品正在開發當中,我們現在還不知道它的準確標題,只知道主人公名叫「春日一番」。《如龍》系列外傳《如龍》系列外傳共有3部,等於是把「正傳」裡的角色再利用所製作的遊戲。不過,「再利用」不代表是小製作,「外傳」的體量不下於「正傳」,也有不少玩家喜愛「外傳」更勝於「正傳」。
  • 「查缺補漏」鞏固你的HTTP知識體系
    「隊頭阻塞」,當http開啟長連接時,共用一個TCP連接,當某個請求時間過長時,其他的請求只能處於阻塞狀態,這就是隊頭阻塞問題。接收方收到二進位幀後,將相同的Stream ID組裝成完整的請求報文和響應報文。
  • 「easy Backend」掌握HTTP請求的精髓
    http請求過程網上有很多博客寫的很詳盡也總結的很好,本文更多地面向前端來淺顯地描述HTTP訪問的過程,幫助大家更好的理解流程。本文有很多概念性的東西,晦澀難懂,有不明確或者存疑的地方歡迎版聊談論。我剪了一段youtube的response header,裡面需要知道的知識點
  • JavaWeb中Cookie會話管理,理解Http無狀態處理機制
    1、《Servlet簡單實現開發部署過程》2、《Servlet部署描述符》3、《通過了解Servlet和Http之間的關係,了解web中http通信使用》4、《通過了解Servlet和Http之間的關係,了解web中http通信使用(二)》
  • 「入坑指南 9」《三國無雙》系列(上)
    今天給大家帶來的是《入坑指南》的第9期,《三國無雙》系列(上)。有什麼意見建議還請大家踴躍留言,謝謝~在《真三國無雙2》到《真三國無雙4》的時代,《猛將傳》是一張單獨的光碟,通常是比本篇(俗稱「無印」)增添了一些遊戲模式、武器道具之類,售價則要低一些;單獨購買《猛將傳》也可以運行遊戲,但玩不到「無印」的內容;想要體驗「無印」+《猛將傳》的完整內容,必須兩張光碟都買,玩遊戲時先把「無印」放入PS2機器內,再換成《猛將傳》的光碟運行遊戲,若關閉電源,則需要重複以上插盤步驟。
  • 網站是如何「看到」用戶的:獨立IP與cookie
    在向具體說明IP和Cookie的使用之前,先向大家介紹一個實用的工具 GA。Cookie,瀏覽器緩存Cookie refers to an HTTP cookie, which is a small piece of data sent from a website and stored on the user’s computer by the user’s web browser
  • JavaWeb——Cookie詳解
    一個同學來過教師,下次再來教室,我們會知道這個同學曾經來過Cookiedemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //伺服器告訴你的時間,把這個時間封裝成為一個信件,你下次帶來,我就知道你來了
  • 重磅推出:「GitHub 精選」使用指南
    而且經常遇到很多同學問:有沒有關於 Java 相關的開源項目,有沒有關於 Go 的開源項目等等,很多類似的問題,為此,我準備給大家整理一個關於「GitHub 精選」的使用指南,旨在幫助大家更好的利用我們這個公眾號。其實,微信公眾號的功能還是很強大的,可能目前有很多關注的讀者對於微信公眾號的功能不太了解,這次為大家分享兩個使用訣竅,即可更加精準的搜索歷史內容。
  • 「入坑指南 5」《XCOM》系列
    大家好,我是傳頌之熊,各位可以叫我「小傳熊」。今天給大家帶來的是《入坑指南》的第5期,《XCOM》系列。這個系列的版本情況稍微有點繞……有什麼意見建議還請大家踴躍留言,謝謝~從1994到2001年,Microprose公司(簡稱「MP」)開發了5部《X-COM》遊戲,其中「Enemy Unknown」、「Terror from the deep」、「Apocalypse」三部屬於正統的策略遊戲,「Interceptor」、「Enforcer」兩部則是射擊和飛行模擬遊戲。
  • 「大川的指南」2020年初iPad選購指南
    本文全篇5000+,預計閱讀時間15分鐘本文內容:iPad選購流程圖選購iPad 的「寫在前邊」各款產品的優劣及特點配件購買指南總結之所以說它們「掌控統領市場如此穩重」,是因為在每個價位段都可以找到對應的產品,同時在每個用戶需求的地方也都能找到對應的產品,而最可怕的是,在每個價位段只有零星甚至說並沒有對手。
  • 「大川的指南」2020年初iPad選購指南
    本文全篇5000+,預計閱讀時間15分鐘本文內容:iPad選購流程圖選購iPad 的「寫在前邊」各款產品的優劣及特點配件購買指南總結之所以說它們「掌控統領市場如此穩重」,是因為在每個價位段都可以找到對應的產品,同時在每個用戶需求的地方也都能找到對應的產品,而最可怕的是,在每個價位段只有零星甚至說並沒有對手。
  • PHP-WEB交互「程式設計師培養之路第四十天」
    >第二節 會話控制會話控制是一種跟蹤用戶的通信方式例如:當一個用戶在請求一個頁面後,再次請求這個頁面,網站是無法知道這個用戶剛才是否曾經來訪問過。cookiecookie是在伺服器端創建,並寫回到客戶端瀏覽器瀏覽器接到指令則在本地臨時文件夾中創建了一個cookie文件,其中保存了你的cookie內容客戶端瀏覽器每次訪問網站時,都會檢測是否有該網站的cookie
  • 超實用 | 企業微信這3個「隱藏」功能,我竟然現在才知道!
    除了一些基礎功能之外,企業微信還有很多超級方便實用的小技巧,是大家不知道的。 比如,企業微信的消息可以同步到微信,多品牌企業對外溝通可以展示不同的簡稱...... 所以,今天我為大家整理出幾個超實用的隱藏功能。
  • Sqlmap Cookie注入 教程
    用工具sqlmap來做cookie注入很簡單,只需要用sqlmap cookie注入命令即可!具體操作如下:1、檢測是否有cookie注入?root@Kali:~# sqlmap -u http://www.xxxxxx.com/Newsshow.asp --cookie "ID=212" --level 3注意:這裡或許很慢,請大家耐心等待即可!
  • iOS和macOS的新版本中的Safari包含完整的第三方Cookie阻止
    Apple的WebKit團隊今天在一篇新博客中表示,macOS 10.15.4和iOS以及iPadOS 13.4中的Safari增強了Apple的智能跟蹤阻止功能,從而可以完全阻止第三方cookie。為了保持對跨站點集成的支持,我們兩年前發布了Storage Access API,以提供經過身份驗證的嵌入的方式,以通過強制用戶控制來獲得cookie訪問。目前,它正在W3C隱私社區小組中進行標準流程。新的Cookie阻止功能可確保在刪除有狀態狀態時無法通過Cookie阻止行為檢測到「智能跟蹤阻止」狀態,並且還可以防止攻擊者看到ITP狀態。