1. 程式人生 > >cookie跨域那些事兒

cookie跨域那些事兒

一個請求從發出到返回,需要瀏覽器和服務端的協調配合。瀏覽器要把自己的請求引數帶給服務端,服務端校驗引數之後,除了返回資料,也可能會順便把請求是否快取,cookie等資訊告訴瀏覽器。當請求是跨域請求的時候,這個過程還要複雜一些。接下來咱們就看看跨域會有什麼問題,又需要前後端進行怎樣的配合。 ### 普通跨域 我有一個朋友,叫小王。前端小王和後端同事小馬準備聯調一個登入的api。假設是`/login`;小王在把登入賬號和密碼都準備好之後,愉快的發起了post提交。結果很意外,請求的響應被瀏覽器攔截了,瀏覽器還貼心的在console上丟擲了一個錯誤。 ![image](https://img2020.cnblogs.com/blog/1016471/202103/1016471-20210328125328626-2092669688.png) 小王翻譯了一下,原來是被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攔截了,並丟擲了下面的錯誤: ![image](https://img2020.cnblogs.com/blog/1016471/202103/1016471-20210329204643891-1530573000.png) 在上面的報錯中,我們看到了 preflight 的單詞。那這又是怎麼回事呢?原來,修改請求體之後,這個跨域請求不再是簡單請求了,需要在發起請求之前先進行 preflight 請求。那麼什麼是簡單請求呢? - 請求方法包括`GET`, `HEAD`, `POST` - response header裡面不能包含[cors安全header](https://fetch.spec.whatwg.org/#cors-safelisted-request-header)以外的header。 - Content-Type 只限於`text/plain`, `multipart/form-data`, `application/x-www-form-urlencoded` 由於json資料的content-type導致這個post請求不再是簡單請求,而對於非簡單請求,之前允許所有域名跨域訪問是被禁止的。所以還是要修改`Access-Control-Allow-Origin`為特定的請求域名。在開發模式下,可能是`http://localhost:3000`之類的。 小馬在重新修改`Access-Control-Allow-Origin`,小王又拿到了登入成功的結果。可以聯調下一個api了。 ### 帶cookie的跨域 登入是基於session的,也就是說,登入成功後,server會通過`set-cookie`,將cookie設定到瀏覽器中,這樣,下次訪問同源下的api時,cookie就會被帶上。 然而,奇怪的是,小王發現登入成功後,呼叫別的介面,cookie並沒有被帶上,導致server無法識別出使用者資訊,最終返回錯誤(狀態碼為401)。 ### withCredentials 原來,瀏覽器發起**跨域請求**的時候,是不會主動帶上cookie的,如果一個請求需要cookie,需要開發者設定一個選項,以fetch api為例: ```js fetch('http://baidu.com:3000', { // ... credentials: true }) ``` 如果使用xhr api來請求,則需要這樣寫: ```js 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(); } } ``` 小王在設定請求之後又發起了一次請求。卻發現cookie還是沒有帶上去。小王只好在MDN繼續檢視資料,發現在set-cookie時需要帶一個sameSite的屬性。 ### sameSite sameSite是為了防止csrf攻擊而產生的屬性,如果不知道啥是CSRF攻擊,可以看我[這篇文章](https://www.cnblogs.com/imgss/p/csrf.html)。由於我們需要在請求中帶上cookie,所以需要在set-cookie時將cookie的sameSite設定為none;又由於將sameSite設定為none時,也需要將Secure設定上,所以請求需要基於https; 小王最後一次請求小馬對api進行了上訴更改,伺服器終於認出請求來自誰,並返回了正確的結果,跨域的踩坑之旅算是告一段落。 ### 總結 很多時候,我們可能只會關注請求體是什麼,響應有沒有正確返回,而忽略了header部分。殊不知,header在快取,web安全,瀏覽器正確解析結果中發揮了重要的作用,比如本文中的一系列`Access-Control-Allow-*`的header。希望本文能對您理解跨域有所幫助。(本文完) ### 參考資料 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS http://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies#samesite_cookies https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Authen