天天看點

跨域怎麼解決?

目錄

    • 同源政策
    • 什麼是源?
    • 什麼是同源政策?
    • 為什麼會有同源政策?
    • 問題的根源
    • 安全原則
    • 什麼是跨域?
    • 關于跨域的幾個問題
    • CORS跨域
    • 簡單請求
    • 複雜請求
    • CORS存在的問題
    • JSONP跨域

同源政策

在說跨域之前,首先需要了解的一個概念就是同源政策。

什麼是源?

源=協定+域名+端口号。

如果兩個url的協定、域名、端口号完全一緻,那麼這兩個url就是同源的。

我們可以通過window.origin或location.origin得到目前源。

https://wang.com
https://ergou.com
//不同源,域名不一緻(記住:隻有完全一模一樣才算同源)

http://wang.com/index.html
http://wang.com/server.php
//同源

localhost 調用 127.0.1 
//不同源
           

什麼是同源政策?

同源政策即: 不同源之間的頁面,不準互相通路資料。

浏覽器規定:如果JS運作在源A裡,那麼就隻能擷取源A的資料,不能擷取源B的資料,即不允許跨域。

假設 wang.com/index.html引用了ergou.com/1.js,那麼就說1.js運作在源wang.com裡,注意,這和ergou.com沒有關系,雖然1.js是從它那裡下載下傳的.是以1.js就隻能擷取wang.com的資料,這就是浏覽器的功能,浏覽器就是故意這樣設計的。

為什麼會有同源政策?

之是以需要使用同源政策,就是為了保護使用者的隐私。

以微信為例,源為 https://user.weixin.com,假設目前使用者已經登入,并且AJAX請求 /friends.json 可以擷取使用者好友清單。

這個時候黑客來了,他把 https://user-winxin.com分享給你,實際上這是一個釣魚網站,你點開這個網頁,這個網頁也請求你的好友清單 https://user.weixin.com/friends.json。

請問,這個時候你的好友清單是不是就被黑客給偷走了?

問題的根源

之是以會出現這個問題,其根源就在于無法區分發送者。

微信裡面的JS和黑客的JS發送到請求幾乎沒有差別(referer差別)

但是如果背景的開發者沒有檢查 referer,那麼就完全沒有差別。

是以,如果沒有同源政策,任何頁面都能偷走微信裡面的資料,甚至是支付寶裡面的餘額。

安全原則

有的小夥伴可能會問,既然referer有差別,那檢查referer不就好了?

安全原則:安全鍊條上的強度取決于安全鍊條上最弱的一環。

同時,萬一這個網站的後端開發者是一個傻叉呢?

是以浏覽器應該主動預防這種偷資料的行為。

總之,為了保護使用者的隐私,浏覽器設定了嚴格的同源政策。如果浏覽器不限制跨域,一定是這個浏覽器出現了bug。

什麼是跨域?

跨域,即浏覽器試圖執行其他網站的腳本。但是由于同源政策的限制,導緻我們無法實作跨域。

關于跨域的幾個問題

  • 為什麼a.wang.com通路wang.com也算跨域?

    因為曆史上,出現過不同的公司共用域名,a.wang.com和wang.com不一定是同一個網站,浏覽器謹慎起見,認為這是不同的源。

  • 為什麼不同端口也算跨域?

    原因同上,一個端口一個公司的情況也不是沒有的。

    安全鍊條的強度取決于最弱的一環,所有和安全相關的問題都要謹慎對待。

  • 為什麼兩個網站的IP一樣,也算跨域?

    原因同上,因為IP也是可以共用的。

  • 為什麼可以跨域使用CSS、JS和圖檔等?

    同源政策限制的是資料通路,我們引用CSS、JS和圖檔的時候,其實并不知道其内容,我們隻是在引用。

CORS跨域

  • 什麼是CORS?

    CORS的全稱是**“跨域資源共享”**(Cross-origin resource sharing)。 它允許浏覽器向跨源伺服器,發出XMLHttpRequest請求,進而克服了AJAX隻能同源使用的限制。

  • 如何了解CORS?

    如果wang.com和ergou.com這兩個網站都是我的,我就是想讓zhen.com去通路ergou.com裡面的資料應該怎麼辦呢?隻需要wang.com在響應頭裡寫ergou.com可以通路即可。

    這就是CORS。實作CORS通信的關鍵是伺服器。隻要伺服器實作了CORS接口,就可以跨源通信。

  • 兩種請求

    CORS跨域分為兩種請求,一種是簡單請求,另外一種就是複雜請求。

簡單請求

隻要滿足以下條件的就是簡單請求:

  1. 請求方式為HEAD、POST 或者 GEThttp頭資訊不超出以下字段:Accept、Accept-Language 、 Content-Language、 Last-Event-ID、 Content-Type(限于三個值:application/x-www-form-urlencoded、multipart/form-data、text/plain)
  2. 簡單請求的實作具體來說就是在資訊頭中加入一個Origin字段:
GET /cors HTTP/1.1
Origin: http://wang.com
Host: api.ergou.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0
           

Origin的作用就是用來說明本次請求來自哪個源,伺服器會根據Origin的值來判斷是否接受本次請求。

如果Origin所表示的源不被伺服器接受,即浏覽器發現回應的資訊頭中沒有Access-Control-Allow-Origin字段,就會自動抛出一個錯誤。

注意:這種錯誤是無法通過狀态碼識别的,這也是通過CORS實作跨域請求的一個弊端。

如果Origin所表示的源被伺服器端所接受,那麼伺服器就會傳回如下響應:

Access-Control-Allow-Origin: http://api.ergou.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
           
  • Access-Control-Allow-Origin :該字段是必須的。它的值要麼是請求時Origin字段的值,要麼是一個*,表示接受任意域名的請求
  • Access-Control-Allow-Credentials: 該字段可選。它的值是一個布爾值,表示是否允許發送Cookie。預設情況下,Cookie不包括在CORS請求之中。設為true,即表示伺服器明确許可,Cookie可以包含在請求中,一起發給伺服器。這個值也隻能設為true,如果伺服器不要浏覽器發送Cookie,删除該字段即可。(注意:如果要發送cookie,不僅要進行上述的設定,還要在AJAX請求中設定withCredentials屬性)
  • Access-Control-Expose-Headers:該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法隻能拿到6個基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必須在Access-Control-Expose-Headers裡面指定。

複雜請求

所謂複雜請求,即不滿足上述條件的請求就是複雜請求。

比如請求的方法是PUT或DELETE,或者Content-Type字段的類型是application/json。

複雜請求首先會發起一個預檢請求,該請求是 option 方法的,通過該請求來知道服務端是否允許跨域請求。

var url = 'http://api.wang.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
           

上面的請求就是一個複雜請求,當浏覽器發現這是一個複雜請求之後,就會主動發出一個預檢請求,詢問伺服器是否允許本次請求。

伺服器收到預檢請求之後,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以後,确認允許跨源請求,才會做出相應的回應。

Access-Control-Allow-Origin: http://api.wang.com
Content-Type: text/html; charset=utf-8
           

CORS存在的問題

不支援IE8/9,如果要在IE8/9使用CORS跨域需要使用XDomainRequest對象來支援CORS。

JSONP跨域

什麼是JSONP?

我們在跨域的時候由于目前的浏覽器不支援 CORS 或者因為某些條件不支援 CORS,我們必須使用另外一種方式來跨域,于是我們就請求一個 JS 檔案,這個 JS 檔案會執行一個回調,回調裡面就有我們需要的資料。

let script = document.createElement('script');

script.src = 'http://www.wang.cn/login?username=wang&callback=callback';

document.body.appendChild(script);

function callback(res) {
  console.log(res);
}
           

回調函數的名字是什麼?

回調的名字是可以随機生成的的一個随機數,我們把這個名字當成 callback 的參數傳給背景,背景會把這個函數再次傳回給我們并執行

JSONP跨域優點

  • 相容ie
  • 可以跨域

JSONP跨域缺點

  • 由于是 script 标簽,是以讀不到 ajax 那麼精确的狀态,不知道狀态碼是什麼,也不知道響應頭是什麼,它隻知道成功和失敗。
  • 不支援post(因為是 script 标簽,是以隻支援 get 請求)

文章中如有不對的地方,歡迎小夥伴們多多指正。謝謝大家~

繼續閱讀