天天看點

SpringMVC解決跨域問題

有個朋友在寫扇貝插件的時候遇到了跨域問題。

于是我對解決跨域問題的方式進行了一番探讨。

問題

API:查詢單詞

URL: https://api.shanbay.com/bdc/search/?word={word}

請求方式: GET

參數: {word}, 必須,要查詢的單詞

報錯為

XMLHttpRequest cannot load http://localhost/home/saveCandidate. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. The response had HTTP status code 404.
           

這就是典型的跨域問題。

但是我在浏覽器裡輸入URL是可以進行查詢單詞的操作的,有什麼不同,即下面兩個問題

  1. 為什麼在浏覽器位址欄輸入URL不會出現跨域問題。
  2. 不在伺服器運作的html是否可以完成一次http請求

經過Google和自己測試

  1. 跨域限制是浏覽器行為,不是伺服器行為。 浏覽器認為位址欄輸入時安全的,是以不限制認為是跨域。
  2. 可以,隻要伺服器配置為所有域都可以進行請求,那麼不在伺服器運作的HTML就可以完成http請求。

什麼是跨域問題

同源政策:

同源指的是域名(或IP),協定,端口都相同,不同源的用戶端腳本(javascript、ActionScript)在沒明确授權的情況下,不能讀寫對方的資源。
URL 解釋 是否跨域
http://www.morethink.cn 原來的URL
http://www.image.morethink.cn 子域名 跨域(cookie也無法通路)
http://morethink.cn 不加www 跨域
https://www.morethink.cn 更改協定
http://www.morethink.cn:8080 更改端口号

原因:

同源政策的目的,是為了保證使用者資訊的安全,防止惡意的網站竊取資料。

設想這樣一種情況:A網站是一家銀行,使用者登入以後,又去浏覽其他網站。如果其他網站可以讀取A網站的Cookie,會發生什麼?

很顯然,如果Cookie包含隐私(比如存款總額),這些資訊就會洩漏。更可怕的是,Cookie往往用來儲存使用者的登入狀态,如果使用者沒有登出,其他網站就可以冒充使用者,為所欲為。因為浏覽器同時還規定,送出表單不受同源政策的限制。

由此可見,"同源政策"是必需的,否則 Cookie 可以共享,網際網路就毫無安全可言了。

同源政策限制以下幾種行為:

  1. Cookie、LocalStorage 和 IndexDB 無法讀取
  2. DOM 和 Js對象無法獲得
  3. AJAX 請求不能發送

模拟跨域問題

測試URL為 http://localhost:80/home/allProductions

可以直接在浏覽器

console

中執行

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:80/home/allProductions',true);
xhr.send();
xhr.onreadystatechange=function() {
    if(xhr.readyState == 4) {
        if(xhr.status == 200) {
          console.log(JSON.parse(xhr.responseText));
        }
    }
}
           

在任意網站打開控制台,執行此段代碼可以模拟跨域請求。

在知乎控制台打開報錯如下

Mixed Content: The page at 'https://www.zhihu.com/question/26376773' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://localhost/home/allProductions'. This request has been blocked; the content must be served over HTTPS.
           
SpringMVC解決跨域問題

因為知乎是https,報錯與普通的http協定不同。

再澄清一下跨域問題:

  1. 并非浏覽器限制了發起跨站請求,而是跨站請求可以正常發起,但是傳回結果被浏覽器攔截了。最好的例子是CRSF跨站攻擊原理,無論是否跨域,請求已經發送到了後端伺服器!
  2. 但是,有些浏覽器不允許從HTTPS的域跨域通路HTTP,比如Chrome和Firefox,這些浏覽器在請求還未發出的時候就會攔截請求,這是一個特例。

在部落格園控制台打開報錯如下

XMLHttpRequest cannot load http://localhost/home/allProductions. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://www.cnblogs.com' is therefore not allowed access.
           

怎麼解決跨域問題

解決方案有很多

  1. 通過jsonp跨域
  2. document.domain + iframe跨域
  3. location.hash + iframe
  4. window.name + iframe跨域
  5. postMessage跨域
  6. 跨域資源共享(CORS)
  7. 前端通過Nginx解決跨域問題
  8. nodejs中間件代理跨域
  9. WebSocket協定跨域

這裡主要介紹SpringMVC解決跨域問題的方式。

  1. JSONP
  2. CORS
  3. WebSocket

可以直接參考Spring MVC 4.1 支援jsonp進行配置你的SpringMVC注解

JSONP 原理

我雖然請求不了json資料,但是我可以請求一個

Content-Type

application/javascript

的JavaScript對象,這樣就可以避免浏覽器的同源政策。

就是當伺服器接受到名為

jsonp

或者

callback

的參數時,傳回

Content-Type: application/javascript

的結果,進而避免浏覽器的同源政策檢測。

在控制台中直接進行測試你的jsonp是否配置成功:

function println(data) {
    console.log(data);
}

var url = "http://localhost:80/home/allProductions?&callback=println";
// 建立script标簽,設定其屬性
var script = document.createElement('script');
script.setAttribute('src', url);
// 把script标簽加入head,此時調用開始
document.getElementsByTagName('head')[0].appendChild(script);
           
SpringMVC解決跨域問題

使用JQuery測試你的jsonp是否配置成功,因為控制台不能直接加載JQuery,需要自己建立html檔案來進行測試:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="http://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>
    <script type="text/javascript">
        function println(data) {
            console.log(data);
            console.log('print');
        }
      function jsonp_test() {
            $.ajax({
                type: "get",
                url: "http://localhost:80/home/allProductions",
                dataType: "jsonp",
                jsonp: "callback",//傳遞給請求處理程式或頁面的,用以獲得jsonp回調函數名的參數名(一般預設為:callback)
                jsonpCallback: "println", //傳回後調用的處理函數
                error: function () { //請求出錯的處理
                    alert("請求出錯");
                }
            });
        }
    </script>
</head>
<body onload="jsonp_test()">
</body>
</html>
           

CORS是一個W3C标準,全稱是"跨域資源共享"(Cross-origin resource sharing)。

它允許浏覽器向跨源伺服器,發出XMLHttpRequest請求,進而克服了AJAX隻能同源使用的限制。

CORS需要浏覽器和伺服器同時支援。

  1. 所有浏覽器都支援該功能,IE浏覽器不能低于IE10。

    整個CORS通信過程,都是浏覽器自動完成,不需要使用者參與。 對于開發者來說,CORS通信與同源的AJAX通信沒有差别,代碼完全一樣。浏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭資訊,有時還會多出一次附加的請求,但使用者不會有感覺。

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

即CORS與普通請求代碼一樣。

CORS與JSONP相比

  1. JSONP隻能實作GET請求,而CORS支援所有類型的HTTP請求。
  2. 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得資料,比起JSONP有更好的錯誤處理。
  3. JSONP主要被老的浏覽器支援,它們往往不支援CORS,而絕大多數現代浏覽器都已經支援了CORS。

@CrossOrigin

注解

此注解既可用于方法也可用于類

源碼如下:

@CrossOrigin(origins = "http://www.zhihu.com")
@RequestMapping(value = "/allProductions", method = RequestMethod.GET)
public Result getAllOldProductions() {

}
           

@CrossOrigin

注解既可注解在方法上,也可注解在類上。

完成配置之後

XML全局配置

所有跨域請求都可以通路

<mvc:cors>
    <mvc:mapping path="/**" />
</mvc:cors>
           

更加細粒度的配置:

<mvc:cors>

    <mvc:mapping path="/api/**"
        allowed-origins="http://domain1.com, http://domain2.com"
        allowed-methods="GET, PUT"
        allowed-headers="header1, header2, header3"
        exposed-headers="header1, header2" allow-credentials="false"
        max-age="123" />

    <mvc:mapping path="/resources/**"
        allowed-origins="http://domain1.com" />

</mvc:cors>
           

WebSocket是一種通信協定,使用ws://(非加密)和wss://(加密)作為協定字首,在2008年誕生,2011年成為國際标準。所有浏覽器都已經支援了。

它的最大特點就是,伺服器可以主動向用戶端推送資訊,用戶端也可以主動向伺服器發送資訊,是真正的雙向平等對話,屬于伺服器推送技術的一種。

該協定不實行同源政策,隻要伺服器支援,就可以通過它進行跨源通信。

請求頭資訊:(多了個 origin)

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
           

響應頭:(如果origin在白名單内)

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
           

相比于HTTP/2

HTTP/2隻是對HTML、CSS等JS資源的傳輸方式進行了優化,并沒有提供新的JS API,不能用于實時傳輸消息,也無法推送指定的資訊。

參考文檔:

    • 浏覽器同源政策及其規避方法
    • 跨域資源共享 CORS 詳解
  1. SpringMVC 跨域解決方法
    • Spring MVC 4.2 增加 CORS 支援
  2. 前端常見跨域解決方案(全)