cookie跨域那些事兒
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
作者 | 飯?zhí)爻?/span>
來(lái)源 | urlify.cn/nm6zue
76套java從入門(mén)到精通實(shí)戰(zhàn)課程分享
一個(gè)請(qǐng)求從發(fā)出到返回,需要瀏覽器和服務(wù)端的協(xié)調(diào)配合。瀏覽器要把自己的請(qǐng)求參數(shù)帶給服務(wù)端,服務(wù)端校驗(yàn)參數(shù)之后,除了返回?cái)?shù)據(jù),也可能會(huì)順便把請(qǐng)求是否緩存,cookie等信息告訴瀏覽器。當(dāng)請(qǐng)求是跨域請(qǐng)求的時(shí)候,這個(gè)過(guò)程還要復(fù)雜一些。接下來(lái)咱們就看看跨域會(huì)有什么問(wèn)題,又需要前后端進(jìn)行怎樣的配合。
普通跨域
我有一個(gè)朋友,叫小王。前端小王和后端同事小馬準(zhǔn)備聯(lián)調(diào)一個(gè)登錄的api。假設(shè)是/login;小王在把登錄賬號(hào)和密碼都準(zhǔn)備好之后,愉快的發(fā)起了post提交。結(jié)果很意外,請(qǐng)求的響應(yīng)被瀏覽器攔截了,瀏覽器還貼心的在console上拋出了一個(gè)錯(cuò)誤。
小王翻譯了一下,原來(lái)是被CORS策略攔截掉了。這個(gè)策略大概意思是說(shuō),服務(wù)端如果允許不同origin的請(qǐng)求,那就需要在返回的response header里面帶上Access-Control-Allow-Origin這個(gè)header。否則瀏覽器在拿到響應(yīng)并發(fā)現(xiàn)響應(yīng)頭里沒(méi)有這個(gè)header時(shí),就會(huì)把響應(yīng)給吞掉,而不會(huì)交給js進(jìn)行下一步處理。
小王把這個(gè)事情告訴了小馬,然后小馬在返回的header中加上了
Access-Control-Allow-Origin: *
現(xiàn)在小王終于可以拿到返回的結(jié)果了。
這里要注意,瀏覽器不是在請(qǐng)求階段就對(duì)請(qǐng)求進(jìn)行攔截,而是正常發(fā)出請(qǐng)求,拿到服務(wù)端的響應(yīng)之后,開(kāi)始查看響應(yīng)header里面有沒(méi)有
Access-Control-Allow-Origin這個(gè)header,如果沒(méi)有,響應(yīng)的結(jié)果就不會(huì)到j(luò)s那里去。
非簡(jiǎn)單請(qǐng)求的跨域
后來(lái)小王覺(jué)得在post中發(fā)送表單格式的body太麻煩,希望使用JSON格式的請(qǐng)求體提交。小馬覺(jué)得就是幾行代碼的事,就同意了。但是小王改成JSON的消息體之后發(fā)現(xiàn)又被CORS攔截了,并拋出了下面的錯(cuò)誤:
在上面的報(bào)錯(cuò)中,我們看到了 preflight 的單詞。那這又是怎么回事呢?原來(lái),修改請(qǐng)求體之后,這個(gè)跨域請(qǐng)求不再是簡(jiǎn)單請(qǐng)求了,需要在發(fā)起請(qǐng)求之前先進(jìn)行 preflight 請(qǐng)求。那么什么是簡(jiǎn)單請(qǐng)求呢?
請(qǐng)求方法包括
GET,HEAD,POSTresponse header里面不能包含cors安全header以外的header。
Content-Type 只限于
text/plain,multipart/form-data,application/x-www-form-urlencoded
由于json數(shù)據(jù)的content-type導(dǎo)致這個(gè)post請(qǐng)求不再是簡(jiǎn)單請(qǐng)求,而對(duì)于非簡(jiǎn)單請(qǐng)求,之前允許所有域名跨域訪問(wèn)是被禁止的。所以還是要修改Access-Control-Allow-Origin為特定的請(qǐng)求域名。在開(kāi)發(fā)模式下,可能是http://localhost:3000之類的。
小馬在重新修改Access-Control-Allow-Origin,小王又拿到了登錄成功的結(jié)果。可以聯(lián)調(diào)下一個(gè)api了。
帶cookie的跨域
登錄是基于session的,也就是說(shuō),登錄成功后,server會(huì)通過(guò)set-cookie,將cookie設(shè)置到瀏覽器中,這樣,下次訪問(wèn)同源下的api時(shí),cookie就會(huì)被帶上。
然而,奇怪的是,小王發(fā)現(xiàn)登錄成功后,調(diào)用別的接口,cookie并沒(méi)有被帶上,導(dǎo)致server無(wú)法識(shí)別出用戶信息,最終返回錯(cuò)誤(狀態(tài)碼為401)。
withCredentials
原來(lái),瀏覽器發(fā)起跨域請(qǐng)求的時(shí)候,是不會(huì)主動(dòng)帶上cookie的,如果一個(gè)請(qǐng)求需要cookie,需要開(kāi)發(fā)者設(shè)置一個(gè)選項(xiàng),以fetch api為例:
fetch('http://baidu.com:3000', {
// ...
credentials: true
})如果使用xhr api來(lái)請(qǐng)求,則需要這樣寫(xiě):
var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain(){
if(invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true; // 帶上cookie
invocation.onreadystatechange = handler;
invocation.send();
}
}小王在設(shè)置請(qǐng)求之后又發(fā)起了一次請(qǐng)求。卻發(fā)現(xiàn)cookie還是沒(méi)有帶上去。小王只好在MDN繼續(xù)查看資料,發(fā)現(xiàn)在set-cookie時(shí)需要帶一個(gè)sameSite的屬性。
sameSite
sameSite是為了防止csrf攻擊而產(chǎn)生的屬性,如果不知道啥是CSRF攻擊,可以看我這篇文章。由于我們需要在請(qǐng)求中帶上cookie,所以需要在set-cookie時(shí)將cookie的sameSite設(shè)置為none;又由于將sameSite設(shè)置為none時(shí),也需要將Secure設(shè)置上,所以請(qǐng)求需要基于https;
小王最后一次請(qǐng)求小馬對(duì)api進(jìn)行了上訴更改,服務(wù)器終于認(rèn)出請(qǐng)求來(lái)自誰(shuí),并返回了正確的結(jié)果,跨域的踩坑之旅算是告一段落。
總結(jié)
很多時(shí)候,我們可能只會(huì)關(guān)注請(qǐng)求體是什么,響應(yīng)有沒(méi)有正確返回,而忽略了header部分。殊不知,header在緩存,web安全,瀏覽器正確解析結(jié)果中發(fā)揮了重要的作用,比如本文中的一系列Access-Control-Allow-*的header。希望本文能對(duì)您理解跨域有所幫助。
粉絲福利:Java從入門(mén)到入土學(xué)習(xí)路線圖
??????

??長(zhǎng)按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
