自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

【必需知道】實用,完整的HTTP cookie指南

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

[[339019]]

Web 開發(fā)中的 cookie 是什么?

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

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

在本文中,主要側重于技術方面:學習如何在前端和后端創(chuàng)建,使用 HTTP cookie。

后端配置

后端示例是Flask編寫的。如果你想跟著學習,可以創(chuàng)建一個新的Python虛擬環(huán)境,移動到其中并安裝Flask

  1. mkdir cookies && cd $_ 
  2.  
  3. python3 -m venv venv 
  4. source venv/bin/activate 
  5.  
  6. pip install Flask 

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

誰創(chuàng)建 cookies ?

首先,cookies 從何而來?誰創(chuàng)建 cookies ?

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

  • 后端是指可以通過以下方式創(chuàng)建 Cookie:
  • 后端實際應用程序的代碼(Python、JavaScript、PHP、Java)

響應請求的Web服務器(Nginx,Apache)

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

  1. Set-Cookie: myfirstcookie=somecookievalue 

什么時候需要創(chuàng)建 cookie?這取決于需求。

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

  1. from flask import Flask, make_response 
  2.  
  3. app = Flask(__name__) 
  4.  
  5.  
  6. @app.route("/index/", methods=["GET"]) 
  7. def index(): 
  8.     response = make_response("Here, take some cookie!"
  9.     response.headers["Set-Cookie"] = "myfirstcookie=somecookievalue" 
  10.     return response 

然后運行應用程序:

  1. FLASK_ENV=development FLASK_APP=flask_app.py flask run 

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

(127.0.0.1:5000是開發(fā)中的 Flask 應用程序的默認偵聽地址/端口)。

Set-Cookie標頭是了解如何創(chuàng)建cookie的關鍵:

  1. response.headers["Set-Cookie"] = "myfirstcookie=somecookievalue" 

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

如何查看 cookies ?

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

 

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

 

 

 

 

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

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

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

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

在 stdout 上顯示 cookie:

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

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

  1. Set-Cookie: myfirstcookie=somecookievalue; HttpOnly 

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

從現(xiàn)在開始,為方便起見,使用Flask的 response.set_cookie() 在后端上創(chuàng)建 cookie。

我有一個 cookie,現(xiàn)在怎么辦?

你的瀏覽器得到一個 cookie。現(xiàn)在怎么辦呢?一旦有了 cookie,瀏覽器就可以將cookie發(fā)送回后端。

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

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

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

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

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

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

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

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

注意:Max-Age優(yōu)先于expires。

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

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

  1. from flask import Flask, make_response, request 
  2.  
  3. app = Flask(__name__) 
  4.  
  5.  
  6. @app.route("/", methods=["GET"]) 
  7. def index(): 
  8.     response = make_response("Here, take some cookie!"
  9.     response.set_cookie(key="id", value="3db4adj3d", path="/about/"
  10.     return response 
  11.  
  12.  
  13. @app.route("/about/", methods=["GET"]) 
  14. def about(): 
  15.     print(request.cookies) 
  16.     return "Hello world!" 
  17.  
  18.  
  19. @app.route("/contact/", methods=["GET"]) 
  20. def contact(): 
  21.     print(request.cookies) 
  22.     return "Hello world!" 

運行該應用程序:

  1. FLASK_ENV=development FLASK_APP=flask_app.py flask run 

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

  1. curl -I http://127.0.0.1:5000/ --cookie-jar cookies 
  2.  
  3. HTTP/1.0 200 OK 
  4. Content-Type: text/html; charset=utf-8 
  5. Content-Length: 23 
  6. Set-Cookie: id=3db4adj3d; Path=/about/ 
  7. Server: Werkzeug/1.0.1 Python/3.8.3 
  8. Date: Wed, 27 May 2020 09:21:37 GMT 

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

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

/about/ 路由并保存 cookit

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

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

  1. ImmutableMultiDict([('id''3db4adj3d')]) 
  2. 127.0.0.1 - - [27/May/2020 11:27:55] "HEAD /about/ HTTP/1.1" 200 - 

正如預期的那樣,cookie 返回到后端?,F(xiàn)在嘗試訪問 /contact/ 路由:

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

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

  1. ImmutableMultiDict([]) 
  2. 127.0.0.1 - - [27/May/2020 11:29:00] "HEAD /contact/ HTTP/1.1" 200 - 

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

這是cookie權限的第一層。

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

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

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

讓我們看一些例子。

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

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

  1. 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:

  1. 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:

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

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

  1. add_header Set-Cookie "cookiename=d0m41n-c00k13; Domain=valentinog.com"

這里使用 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:

  1. 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:

  1. Set-Cookie: coookiename=subd0m41n-c00k13 

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

  1. response.set_cookie(key="coookiename", value="subd0m41n-c00k13"

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

 

現(xiàn)在,我們有來自serene-bastion-01422.herokuapp.com 的 cookie, 那 cookie 現(xiàn)在應該送到哪里?

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

 

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

 

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

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

一旦瀏覽器接受了cookie,并且即將發(fā)出請求,它就會說:

  • 如果請求主機與我在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請求,用于獲取數據并將其發(fā)送回后端。

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

  1. from flask import Flask, make_response, render_template 
  2.  
  3. app = Flask(__name__) 
  4.  
  5.  
  6. @app.route("/", methods=["GET"]) 
  7. def index(): 
  8.     return render_template("index.html"
  9.  
  10.  
  11. @app.route("/get-cookie/", methods=["GET"]) 
  12. def get_cookie(): 
  13.     response = make_response("Here, take some cookie!"
  14.     response.set_cookie(key="id", value="3db4adj3d"
  15.     return response 

以下是 templates/index.html 模板:

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <title>Title</title> 
  6. </head> 
  7. <body> 
  8. <button>FETCH</button> 
  9. </body> 
  10. <script src="{{ url_for('static', filename='index.js') }}"></script> 
  11. </html> 

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

  1. const button = document.getElementsByTagName("button")[0]; 
  2.  
  3. button.addEventListener("click"function() { 
  4.   getACookie(); 
  5. }); 
  6.  
  7. function getACookie() { 
  8.   fetch("/get-cookie/"
  9.     .then(response => { 
  10.       // make sure to check response.ok in the real world! 
  11.       return response.text(); 
  12.     }) 
  13.     .then(text => console.log(text)); 

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

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

  1. from flask import Flask, make_response, request, render_template, jsonify 
  2.  
  3. app = Flask(__name__) 
  4.  
  5.  
  6. @app.route("/", methods=["GET"]) 
  7. def index(): 
  8.     return render_template("index.html"
  9.  
  10.  
  11. @app.route("/get-cookie/", methods=["GET"]) 
  12. def get_cookie(): 
  13.     response = make_response("Here, take some cookie!"
  14.     response.set_cookie(key="id", value="3db4adj3d"
  15.     return response 
  16.  
  17.  
  18. @app.route("/api/cities/", methods=["GET"]) 
  19. def cities(): 
  20.     if request.cookies["id"] == "3db4adj3d"
  21.         cities = [{"name""Rome""id": 1}, {"name""Siena""id": 2}] 
  22.         return jsonify(cities) 
  23.     return jsonify(msg="Ops!"

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

  1. const button = document.getElementsByTagName("button")[0]; 
  2.  
  3. button.addEventListener("click"function() { 
  4.   getACookie().then(() => getData()); 
  5. }); 
  6.  
  7. function getACookie() { 
  8.   return fetch("/get-cookie/").then(response => { 
  9.     // make sure to check response.ok in the real world! 
  10.     return Promise.resolve("All good, fetch the data"); 
  11.   }); 
  12.  
  13. function getData() { 
  14.   fetch("/api/cities/"
  15.     .then(response => { 
  16.       // make sure to check response.ok in the real world! 
  17.       return response.json(); 
  18.     }) 
  19.     .then(json => console.log(json)); 

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

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

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

這是因為默認情況下,F(xiàn)etch 僅在請求到達觸發(fā)請求的來源時才發(fā)送憑據,即 Cookie。

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

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

  1. FLASK_ENV=development FLASK_APP=flask_app.py flask run 

現(xiàn)在,在 Flask 應用程序之外的其他文件夾中,創(chuàng)建index.html:

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <title>Title</title> 
  6. </head> 
  7. <body> 
  8. <button>FETCH</button> 
  9. </body> 
  10. <script src="index.js"></script> 
  11. </html> 

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

  1. button.addEventListener("click"function() { 
  2.   getACookie().then(() => getData()); 
  3. }); 
  4.  
  5. function getACookie() { 
  6.   return fetch("http://localhost:5000/get-cookie/").then(response => { 
  7.     // make sure to check response.ok in the real world! 
  8.     return Promise.resolve("All good, fetch the data"); 
  9.   }); 
  10.  
  11. function getData() { 
  12.   fetch("http://localhost:5000/api/cities/"
  13.     .then(response => { 
  14.       // make sure to check response.ok in the real world! 
  15.       return response.json(); 
  16.     }) 
  17.     .then(json => console.log(json)); 

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

  1. npx serve 

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

  1. 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 的限制。

處理 CORSCORS

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

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

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

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

  1. pip install flask-cors 

然后將 CORS 應用于 Flask:

  1. from flask import Flask, make_response, request, render_template, jsonify 
  2. from flask_cors import CORS 
  3.  
  4. app = Flask(__name__) 
  5. CORS(app=app) 
  6.  
  7.  
  8. @app.route("/", methods=["GET"]) 
  9. def index(): 
  10.     return render_template("index.html"
  11.  
  12.  
  13. @app.route("/get-cookie/", methods=["GET"]) 
  14. def get_cookie(): 
  15.     response = make_response("Here, take some cookie!"
  16.     response.set_cookie(key="id", value="3db4adj3d"
  17.     return response 
  18.  
  19.  
  20. @app.route("/api/cities/", methods=["GET"]) 
  21. def cities(): 
  22.     if request.cookies["id"] == "3db4adj3d"
  23.         cities = [{"name""Rome""id": 1}, {"name""Siena""id": 2}] 
  24.         return jsonify(cities) 
  25.     return jsonify(msg="Ops!"

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

  1. 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” 標簽中的請求來確認,沒有發(fā)送此類Cookie:

 

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

如果沒有這個標志,F(xiàn)etch 就會忽略 cookie,可以這樣修復:

  1. const button = document.getElementsByTagName("button")[0]; 
  2.  
  3. button.addEventListener("click"function() { 
  4.   getACookie().then(() => getData()); 
  5. }); 
  6.  
  7. function getACookie() { 
  8.   return fetch("http://localhost:5000/get-cookie/", { 
  9.     credentials: "include" 
  10.   }).then(response => { 
  11.     // make sure to check response.ok in the real world! 
  12.     return Promise.resolve("All good, fetch the data"); 
  13.   }); 
  14.  
  15. function getData() { 
  16.   fetch("http://localhost:5000/api/cities/", { 
  17.     credentials: "include" 
  18.   }) 
  19.     .then(response => { 
  20.       // make sure to check response.ok in the real world! 
  21.       return response.json(); 
  22.     }) 
  23.     .then(json => console.log(json)); 

credentials: "include" 必須在第一個 Fetch 請求中出現(xiàn),才能將Cookie保存在瀏覽器的Cookie storage 中:

  1. fetch("http://localhost:5000/get-cookie/", { 
  2.     credentials: "include" 
  3.   }) 

它還必須在第二個請求時出現(xiàn),以允許將cookie傳輸回后端

  1. fetch("http://localhost:5000/api/cities/", { 
  2.   credentials: "include" 
  3. }) 

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

  1. 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標頭。

  1. CORS(app=app, supports_credentials=True

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

  • credentials: "include" 用于前端的 fetch 請求中
  • Access-Control-Allow-Credentials 和 Access-Control-Allow-Origin 用于后端

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

Cookie 的 Secure 屬性

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

可以這樣設置 Secure 屬性

  1. response.set_cookie(key="id", value="3db4adj3d", secure=True

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

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

相反,通過HTTPS,cookie 出現(xiàn)在cookie jar中:

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

cookie jar 文件:

  1. 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代碼會自動執(zhí)行,從而達到攻擊的目的。如,盜取用戶Cookie、破壞頁面結構、重定向到其它網站等。

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

  1. Set-Cookie: "id=3db4adj3d; HttpOnly" 

在 Flask 中

  1. response.set_cookie(key="id", value="3db4adj3d", 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

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

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

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

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

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

 

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

第三方 Cookie 除了用于 CSRF 攻擊,還可以用于用戶追蹤。比如,F(xiàn)acebook 在第三方網站插入一張看不見的圖片。

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

使用 SameSite 屬性

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

  • Strict
  • Lax
  • None

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

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

這個規(guī)則過于嚴格,可能造成非常不好的用戶體驗。比如,當前網頁有一個 GitHub 鏈接,用戶點擊跳轉就不會帶有 GitHub 的 Cookie,跳轉過去總是未登陸狀態(tài)。

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

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

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

 

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

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

下面的設置無效。

  1. Set-Cookie: widget_session=abc123; SameSite=None 

下面的設置有效。

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

Cookies 和 認證

身份驗證是 web 開發(fā)中最具挑戰(zhàn)性的任務之一。關于這個主題似乎有很多困惑,因為JWT中的基于令牌的身份驗證似乎要取代“舊的”、可靠的模式,如基于會話的身份驗證。

來看看 cookie 在這里扮演什么角色。

基于會話的身份驗證

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

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

型的會話 cookie 如下所示:

  1. 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。

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

基于會話的身份驗證是有狀態(tài)的,因為后端必須跟蹤每個用戶的會話。這些會話的存儲可能是:

  • 數據庫
  • 像 Redis 這樣的鍵/值存儲
  • 文件系統(tǒng)

在這三個會話存儲中,Redis 之類應優(yōu)先于數據庫或文件系統(tǒng)。

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

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

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

只要能使用就使用它?;跁挼纳矸蒡炞C是一種最簡單、安全、直接的網站身份驗證形式。默認情況下,它可以在Django等所有流行的web框架上使用。

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

關于 JWT 的說明

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

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

  • 前端將憑證發(fā)送到后端
  • 后端檢查憑證并發(fā)回令牌
  • 前端在每個后續(xù)請求上帶上該令牌

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

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

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

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

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

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

那SameSite=Lax呢?此模式允許使用安全的HTTP方法(即GET,HEAD,OPTIONS和TRACE)將 cookie發(fā)送回去。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

本文轉載自微信公眾號「大遷世界」,可以通過以下二維碼關注。轉載本文請聯(lián)系大遷世界公眾號。

 

責任編輯:武曉燕 來源: 大遷世界
相關推薦

2011-02-23 15:34:33

軟件開發(fā)軟件模型設計

2018-06-11 15:30:56

2018-06-27 15:14:31

HTTP編碼內容

2018-07-12 15:30:03

HTTP緩存機制

2022-10-11 08:48:08

HTTP狀態(tài)碼瀏覽器

2010-08-27 15:24:56

2022-08-09 11:10:06

網絡滲透測試安全漏洞

2022-09-13 08:39:22

Bash腳本

2016-09-06 22:05:41

HttpCookieWeb

2021-10-27 08:00:00

DevSecOps開發(fā)安全

2023-11-29 09:00:00

KubernetesDevOps

2024-07-03 14:14:07

2023-06-11 15:51:13

2024-08-05 09:58:24

2020-09-22 12:22:32

Windows TerWindowsLinux

2010-11-26 09:57:59

2013-07-24 13:44:25

APP開發(fā)者改變

2021-11-23 16:00:56

DevOps價值流價值流優(yōu)化

2023-12-26 08:00:00

微前端React

2020-04-29 14:30:35

HTTPHTTPS前端
點贊
收藏

51CTO技術棧公眾號