有個朋友在寫扇貝插件的時候遇到了跨域問題。
于是我對解決跨域問題的方式進行了一番探讨。
問題
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是可以進行查詢單詞的操作的,有什麼不同,即下面兩個問題
- 為什麼在浏覽器位址欄輸入URL不會出現跨域問題。
- 不在伺服器運作的html是否可以完成一次http請求
經過Google和自己測試
- 跨域限制是浏覽器行為,不是伺服器行為。 浏覽器認為位址欄輸入時安全的,是以不限制認為是跨域。
- 可以,隻要伺服器配置為所有域都可以進行請求,那麼不在伺服器運作的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 可以共享,網際網路就毫無安全可言了。
同源政策限制以下幾種行為:
- Cookie、LocalStorage 和 IndexDB 無法讀取
- DOM 和 Js對象無法獲得
- 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.

因為知乎是https,報錯與普通的http協定不同。
再澄清一下跨域問題:
- 并非浏覽器限制了發起跨站請求,而是跨站請求可以正常發起,但是傳回結果被浏覽器攔截了。最好的例子是CRSF跨站攻擊原理,無論是否跨域,請求已經發送到了後端伺服器!
- 但是,有些浏覽器不允許從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.
怎麼解決跨域問題
解決方案有很多
- 通過jsonp跨域
- document.domain + iframe跨域
- location.hash + iframe
- window.name + iframe跨域
- postMessage跨域
- 跨域資源共享(CORS)
- 前端通過Nginx解決跨域問題
- nodejs中間件代理跨域
- WebSocket協定跨域
這裡主要介紹SpringMVC解決跨域問題的方式。
- JSONP
- CORS
- 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);
使用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需要浏覽器和伺服器同時支援。
-
所有浏覽器都支援該功能,IE浏覽器不能低于IE10。
整個CORS通信過程,都是浏覽器自動完成,不需要使用者參與。 對于開發者來說,CORS通信與同源的AJAX通信沒有差别,代碼完全一樣。浏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭資訊,有時還會多出一次附加的請求,但使用者不會有感覺。
- 實作CORS通信的關鍵是伺服器。隻要伺服器實作了CORS接口,就可以跨源通信。
即CORS與普通請求代碼一樣。
CORS與JSONP相比
- JSONP隻能實作GET請求,而CORS支援所有類型的HTTP請求。
- 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得資料,比起JSONP有更好的錯誤處理。
- JSONP主要被老的浏覽器支援,它們往往不支援CORS,而絕大多數現代浏覽器都已經支援了CORS。
@CrossOrigin
注解
@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 詳解
- SpringMVC 跨域解決方法
- Spring MVC 4.2 增加 CORS 支援
- 前端常見跨域解決方案(全)