Javascript 跨域問題小記

1、同源策略

同源策略是有 Netscape 提出的一個著名的安全策略,現在所有的支持 javascript 的瀏覽器都會使用這個策略。

所謂同源是指主機名、協議、端口相同:

  • 相同的主機名
  • 相同的協議
  • 相同的端口

三者必須同時滿足,只要主機名、協議、端口三者其中之一不同,就為不同的源。

同源策略限制了一個源中加載文本或者腳本與來自其他源中資源的交互方式,簡而言之就是一個源上的 js 只能訪問當前源的資源。

同源策略以源為邊界,把資源分隔開,從而保護用戶的信息安全。

2、跨域的方式

那么,在如今微服務興起的情況下,往往很多業務調用必須跨越同源限制。比如,某商城服務部署在 www.xmall.com,而其支付服務可能部署在 www.xpay.com,因此必須要有方式可以繞過同源策略這堵墻。

常用的 js 跨域方式主要有:

  • 修改 document.domain 跨子域
  • 通過 jsonp 跨域
  • 通過 html5 的 window.postMessage 跨域
  • 通過 CORS 跨域

另外,還可以通過 iframe + window.name 或者 iframe + window.location.hash 進行跨域。

以下涉及示例均使用 http://www.aaa.com/a.html 跨域訪問 http://www.bbb.com/b.html 的數據。

3、修改 document.domain 跨子域

www.aaa.compay.aaa.com 是不同域的,要使他們可以跨域訪問,可以通過修改 document.domain 來實現。即在兩個頁面中都設置:

document.domain = "aaa.com";

這里有個限制就是,document.domain 只能向父域修改,也就是說 www.aaa.com 改為 aaa.com 是允許的,但 aaa.com 改為 www.abc.xyz 則是不被允許的。這也限制了修改 document.domain 方式只能用于跨子域訪問。

4、通過 jsonp 跨域

js 腳本的“源”與它存儲的地址無關,而是取決于腳本被加載的頁面。例如我們在 http://www.aaa.com/a.html 中引入:

<script src="http://cdn.staticfile.org/jquery/1.11.1/jquery.min.js"></script>

那么腳本與 a.html 頁面是同源的,也就是說,腳本的源是 http://www.aaa.com.

PS: 除了 script, img, iframe, link 等都具有跨域加載資源的能力。

jsonp 正是利用 script 標簽沒有跨域限制的特性,通過在 src 的 url 的參數上附加回調函數名字,然后服務器接收回調函數名字并返回一個包含數據的回調函數。

如:

<script>
function callback(data) {
    alert(data.message);
}
</script>
<script src="http://www.bbb.com/b.html?callback=callback"></script>

我們只要服務器端 b.html 輸出:

callback({"message":"test jsonp ok"})

頁面即會執行并彈出 "test jsonp ok"。

jQuery 對此做了很好的支持:

$.ajax({
    url:'http://www.bbb.com/b.html',
    dataType:"jsonp",
    jsonp:"callback",
    success:function(data){
        // callback logic
    }
});

// 或者簡化方式
$.getJSON("http://www.bbb.com/b.html?callback=?", null, 
    function(data) {
        // callback logic
    }
});

** <span style="color:red">需要特別強調的是,jsonp 方式只能用于 GET 方法! </span>**

5、通過html5 window.postMessage 進行跨域

假設在a.html里嵌套個

    <iframe src="http://www.bbbb.com/b.html" frameborder="0"></iframe>

在這兩個頁面里互相通信

a.html

window.onload = function() {
    window.addEventListener("message", function(e) {
        alert(e.data);
    });

    window.frames[0].postMessage("b data", "http://www.b.com/b.html");
}

b.html

window.onload = function() {
    window.addEventListener("message", function(e) {
        alert(e.data);
    });
    window.parent.postMessage("a data", "http://www.a.com/a.html");
}

這樣打開 a.html 頁面就先彈出 a data,再彈出 b data.

6、通過 CORS 進行跨域

CORS 是W3C XMLHttpRequest Level 2 里規定的一種跨域方式。CORS 規范請參考 CORS 規范

CORS 旨在定義一種規范讓瀏覽器在接收到從提供者獲取的資源時能夠正決定是否應該將此資源分發給消費者作進一步處理。CROS利用資源提供者的顯式授權來決定目標資源是否應該與消費者共享。換句話說,瀏覽器需要得到提供者的授權之后才會將其提供的資源分發給消費者。那么,資源的提供者如何進行資源的授權,并將授權的結果告訴瀏覽器呢?

一個 CORS 請求大致過程如下:

  1. 消費者發送一個 Origin 報頭到提供者端:Origin: http://www.bbb.com

  2. 提供者發送一個 Access-Control-Allow-Origin 響應報頭給消費者,如果值為 * 或 Origin 對應的站點,則表示同意共享資源給消費者,如果值為 null 或者不存在 Access-Control-Allow-Origin 報頭,則表示不同意共享資源給消費者;

  3. 瀏覽器根據提供者的響應報文判斷是否允許消費者跨域訪問到提供者源。

例如,一個Servlet C0RS響應圖示如下:

CORS 是在支持這個規范的瀏覽器里,javascript 的寫法和不跨域的 ajax 寫法一模一樣。

6.1、CORS 的瀏覽器支持

Cors Browser Support

6.2、CORS 的服務器端配置

6.2.1 Apache

Header set Access-Control-Allow-Origin "*"

6.2.2 Nginx

#
# Wide-open CORS config for nginx
#
location / {
     if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        #
        # Om nom nom cookies
        #
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        #
        # Custom headers and headers various browsers *should* be OK with but aren't
        #
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        #
        # Tell client that this pre-flight info is valid for 20 days
        #
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;
        return 204;
     }
     if ($request_method = 'POST') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
     }
     if ($request_method = 'GET') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
     }
}

6.2.3 ExpressJS

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

app.get('/', function(req, res, next) {
  // Handle the get for this route
});

app.post('/', function(req, res, next) {
 // Handle the post for this route
});

6.2.4 Tomcat

Tomcat 7.0.41 版本之后提供了一個 CorsFilter 以支持 CORS,詳情猛擊

http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#CORS_Filter

下面是一段最簡單的 CORS 配置:

<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

如果希望自己實現一個 CORS 過濾器,可參考 tomcat 的實現或者下面這個鏈接:

https://github.com/eBay/cors-filter/blob/master/src/main/java/org/ebaysf/web/cors/CORSFilter.java

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容