cookie跨域那些事兒
一個請求從發出到返回,需要瀏覽器和服務端的協調配合。瀏覽器要把自己的請求參數帶給服務端,服務端校驗參數之後,除了返回數據,也可能會順便把請求是否緩存,cookie等信息告訴瀏覽器。當請求是跨域請求的時候,這個過程還要複雜一些。接下來咱們就看看跨域會有什麼問題,又需要前後端進行怎樣的配合。
普通跨域
我有一個朋友,叫小王。前端小王和後端同事小馬準備聯調一個登錄的api。假設是/login
;小王在把登錄賬號和密碼都準備好之後,愉快的發起了post提交。結果很意外,請求的響應被瀏覽器攔截了,瀏覽器還貼心的在console上拋出了一個錯誤。
小王翻譯了一下,原來是被CORS策略攔截掉了。這個策略大概意思是說,服務端如果允許不同origin的請求,那就需要在返回的response header裏面帶上Access-Control-Allow-Origin
這個header。否則瀏覽器在拿到響應並發現響應頭裡沒有這個header時,就會把響應給吞掉,而不會交給js進行下一步處理。
小王把這個事情告訴了小馬,然後小馬在返回的header中加上了
Access-Control-Allow-Origin: *
現在小王終於可以拿到返回的結果了。
這裡要注意,瀏覽器不是在請求階段就對請求進行攔截,而是正常發出請求,拿到服務端的響應之後,開始查看響應header裏面有沒有
Access-Control-Allow-Origin
這個header,如果沒有,響應的結果就不會到js那裡去。
非簡單請求的跨域
後來小王覺得在post中發送表單格式的body太麻煩,希望使用JSON格式的請求體提交。小馬覺得就是幾行代碼的事,就同意了。但是小王改成JSON的消息體之後發現又被CORS攔截了,並拋出了下面的錯誤:
在上面的報錯中,我們看到了 preflight 的單詞。那這又是怎麼回事呢?原來,修改請求體之後,這個跨域請求不再是簡單請求了,需要在發起請求之前先進行 preflight 請求。那麼什麼是簡單請求呢?
- 請求方法包括
GET
,HEAD
,POST
- response header裏面不能包含cors安全header以外的header。
- Content-Type 只限於
text/plain
,multipart/form-data
,application/x-www-form-urlencoded
由於json數據的content-type導致這個post請求不再是簡單請求,而對於非簡單請求,之前允許所有域名跨域訪問是被禁止的。所以還是要修改Access-Control-Allow-Origin
為特定的請求域名。在開發模式下,可能是//localhost:3000
之類的。
小馬在重新修改Access-Control-Allow-Origin
,小王又拿到了登錄成功的結果。可以聯調下一個api了。
帶cookie的跨域
登錄是基於session的,也就是說,登錄成功後,server會通過set-cookie
,將cookie設置到瀏覽器中,這樣,下次訪問同源下的api時,cookie就會被帶上。
然而,奇怪的是,小王發現登錄成功後,調用別的接口,cookie並沒有被帶上,導致server無法識別出用戶信息,最終返回錯誤(狀態碼為401)。
withCredentials
原來,瀏覽器發起跨域請求的時候,是不會主動帶上cookie的,如果一個請求需要cookie,需要開發者設置一個選項,以fetch api為例:
fetch('//baidu.com:3000', {
// ...
credentials: true
})
如果使用xhr api來請求,則需要這樣寫:
var invocation = new XMLHttpRequest();
var url = '//bar.other/resources/credentialed-content/';
function callOtherDomain(){
if(invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true; // 帶上cookie
invocation.onreadystatechange = handler;
invocation.send();
}
}
小王在設置請求之後又發起了一次請求。卻發現cookie還是沒有帶上去。小王只好在MDN繼續查看資料,發現在set-cookie時需要帶一個sameSite的屬性。
sameSite
sameSite是為了防止csrf攻擊而產生的屬性,如果不知道啥是CSRF攻擊,可以看我這篇文章。由於我們需要在請求中帶上cookie,所以需要在set-cookie時將cookie的sameSite設置為none;又由於將sameSite設置為none時,也需要將Secure設置上,所以請求需要基於https;
小王最後一次請求小馬對api進行了上訴更改,服務器終於認出請求來自誰,並返回了正確的結果,跨域的踩坑之旅算是告一段落。
總結
很多時候,我們可能只會關注請求體是什麼,響應有沒有正確返回,而忽略了header部分。殊不知,header在緩存,web安全,瀏覽器正確解析結果中發揮了重要的作用,比如本文中的一系列Access-Control-Allow-*
的header。希望本文能對您理解跨域有所幫助。(本文完)
參考資料
//developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
//www.ruanyifeng.com/blog/2019/09/cookie-samesite.html
//developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies#samesite_cookies
//developer.mozilla.org/en-US/docs/Web/API/Document/cookie
//developer.mozilla.org/zh-CN/docs/Web/HTTP/Authentication