有個朋友在寫扇貝插件的時候遇到了跨域問題。
於是我對解決跨域問題的方式進行了一番探討。
問題
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
JSONP
可以直接參考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
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(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
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,不能用於實時傳輸消息,也無法推送指定的信息。
參考文檔:
- 跨域
- SpringMVC 跨域解決方法
- 前端常見跨域解決方案(全)