原文:http://www.cnblogs.com/JChen666/p/3399951.html
1.同源策略如下:
URL | 說明 | 是否允許通信 |
---|---|---|
http://www.a.com/a.js http://www.a.com/b.js |
同一域名下 | 允許 |
http://www.a.com/lab/a.js http://www.a.com/script/b.js |
同一域名下不同文件夾 | 允許 |
http://www.a.com:8000/a.js http://www.a.com/b.js |
同一域名,不同端口 | 不允許 |
http://www.a.com/a.js https://www.a.com/b.js |
同一域名,不同協議 | 不允許 |
http://www.a.com/a.js http://70.32.92.74/b.js |
域名和域名對應ip | 不允許 |
http://www.a.com/a.js http://script.a.com/b.js |
主域相同,子域不同 | 不允許 |
http://www.a.com/a.js http://a.com/b.js |
同一域名,不同二級域名(同上) | 不允許(cookie這種情況下也不允許訪問) |
http://www.cnblogs.com/a.js http://www.a.com/b.js |
不同域名 | 不允許 |
-
特別注意兩點: 第一, 如果是協議和端口造成的跨域問題“前台”是無能為力的, 第二: 在跨域問題上,域僅僅是通過“URL的首部”來識別而不會去嘗試判斷相同的ip地址對應着兩個域或兩個域是否在同一個ip上。
2. 前端解決跨域問題
1> document.domain + iframe (只有在主域相同的時候才能使用該方法)
1) 在www.a.com/a.html中:
document.domain = 'a.com'; var ifr = document.createElement('iframe'); ifr.src = 'http://www.script.a.com/b.html'; ifr.display = none; document.body.appendChild(ifr); ifr.onload = function(){ var doc = ifr.contentDocument || ifr.contentWindow.document; //在這里操作doc,也就是b.html ifr.onload = null; };
2) 在www.script.a.com/b.html中:
document.domain = 'a.com';
2> 動態創建script
這個沒什么好說的,因為script標簽不受同源策略的限制。
function loadScript(url, func) { var head = document.head || document.getElementByTagName('head')[0]; var script = document.createElement('script'); script.src = url; script.onload = script.onreadystatechange = function(){ if(!this.readyState || this.readyState=='loaded' || this.readyState=='complete'){ func(); script.onload = script.onreadystatechange = null; } }; head.insertBefore(script, 0); } window.baidu = { sug: function(data){ console.log(data); } } loadScript('http://suggestion.baidu.com/su?wd=w',function(){console.log('loaded')}); //我們請求的內容在哪里? //我們可以在chorme調試面板的source中看到script引入的內容
3> location.hash + iframe
原理是利用location.hash來進行傳值。
假設域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html傳遞信息。
1) cs1.html首先創建自動創建一個隱藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html頁面
2) cs2.html響應請求后再將通過修改cs1.html的hash值來傳遞數據
3) 同時在cs1.html上加一個定時器,隔一段時間來判斷location.hash的值有沒有變化,一旦有變化則獲取獲取hash值
注:由於兩個頁面不在同一個域下IE、Chrome不允許修改parent.location.hash的值,所以要借助於a.com域名下的一個代理iframe
代碼如下:
先是a.com下的文件cs1.html文件:
function startRequest(){ var ifr = document.createElement('iframe'); ifr.style.display = 'none'; ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo'; document.body.appendChild(ifr); } function checkHash() { try { var data = location.hash ? location.hash.substring(1) : ''; if (console.log) { console.log('Now the data is '+data); } } catch(e) {}; } setInterval(checkHash, 2000);
cnblogs.com域名下的cs2.html:
//模擬一個簡單的參數處理操作 switch(location.hash){ case '#paramdo': callBack(); break; case '#paramset': //do something…… break; } function callBack(){ try { parent.location.hash = 'somedata'; } catch (e) { // ie、chrome的安全機制無法修改parent.location.hash, // 所以要利用一個中間的cnblogs域下的代理iframe var ifrproxy = document.createElement('iframe'); ifrproxy.style.display = 'none'; ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata'; // 注意該文件在"a.com"域下 document.body.appendChild(ifrproxy); } }
a.com下的域名cs3.html
//因為parent.parent和自身屬於同一個域,所以可以改變其location.hash的值 parent.parent.location.hash = self.location.hash.substring(1);
4> window.name + iframe
window.name 的美妙之處:name 值在不同的頁面(甚至不同域名)加載后依舊存在,並且可以支持非常長的 name 值(2MB)。
1) 創建a.com/cs1.html
2) 創建a.com/proxy.html,並加入如下代碼
<head> <script> function proxy(url, func){ var isFirst = true, ifr = document.createElement('iframe'), loadFunc = function(){ if(isFirst){ ifr.contentWindow.location = 'http://a.com/cs1.html'; isFirst = false; }else{ func(ifr.contentWindow.name); ifr.contentWindow.close(); document.body.removeChild(ifr); ifr.src = ''; ifr = null; } }; ifr.src = url; ifr.style.display = 'none'; if(ifr.attachEvent) ifr.attachEvent('onload', loadFunc); else ifr.onload = loadFunc; document.body.appendChild(iframe); } </script> </head> <body> <script> proxy('http://www.baidu.com/', function(data){ console.log(data); }); </script> </body>
3 在b.com/cs1.html中包含:
<script> window.name = '要傳送的內容'; </script>
5> postMessage(HTML5中的XMLHttpRequest Level 2中的API)
1) a.com/index.html中的代碼:
<script type="text/javascript"> window.onload = function() { var ifr = document.getElementById('ifr'); var targetOrigin = 'http://b.com'; // 若寫成'http://b.com/c/proxy.html'效果一樣 // 若寫成'http://c.com'就不會執行postMessage了 ifr.contentWindow.postMessage('I was there!', targetOrigin); }; </script>
2) b.com/index.html中的代碼:
<script type="text/javascript"> window.addEventListener('message', function(event){ // 通過origin屬性判斷消息來源地址 if (event.origin == 'http://a.com') { alert(event.data); // 彈出"I was there!" alert(event.source); // 對a.com、index.html中window對象的引用 // 但由於同源策略,這里event.source不可以訪問window對象 } }, false); </script>
6> CORS
CORS背后的思想,就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。
IE中對CORS的實現是xdr
var xdr = new XDomainRequest(); xdr.onload = function(){ console.log(xdr.responseText); } xdr.open('get', 'http://www.baidu.com'); ...... xdr.send(null);
其它瀏覽器中的實現就在xhr中
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if(xhr.readyState == 4){ if(xhr.status >= 200 && xhr.status < 304 || xhr.status == 304){ console.log(xhr.responseText); } } } xhr.open('get', 'http://www.baidu.com'); ...... xhr.send(null);
實現跨瀏覽器的CORS
function createCORS(method, url){ var xhr = new XMLHttpRequest(); if('withCredentials' in xhr){ xhr.open(method, url, true); }else if(typeof XDomainRequest != 'undefined'){ var xhr = new XDomainRequest(); xhr.open(method, url); }else{ xhr = null; } return xhr; } var request = createCORS('get', 'http://www.baidu.com'); if(request){ request.onload = function(){ ...... }; request.send(); }
7> JSONP
JSONP包含兩部分:回調函數和數據。
回調函數是當響應到來時要放在當前頁面被調用的函數。
數據就是傳入回調函數中的json數據,也就是回調函數的參數了。
function handleResponse(response){ console.log('The responsed data is: '+response.data); } var script = document.createElement('script'); script.src = 'http://www.baidu.com/json/?callback=handleResponse'; document.body.insertBefore(script, document.body.firstChild); /*handleResonse({"data": "zhe"})*/ //原理如下: //當我們通過script標簽請求時 //后台就會根據相應的參數(json,handleResponse) //來生成相應的json數據(handleResponse({"data": "zhe"})) //最后這個返回的json數據(代碼)就會被放在當前js文件中被執行 //至此跨域通信完成
jsonp雖然很簡單,但是有如下缺點:
1)安全問題(請求代碼中可能存在安全隱患)
2)要確定jsonp請求是否失敗並不容易
8> web sockets
web sockets是一種瀏覽器的API,它的目標是在一個單獨的持久連接上提供全雙工、雙向通信。(同源策略對web sockets不適用)
web sockets原理:在JS創建了web socket之后,會有一個HTTP請求發送到瀏覽器以發起連接。取得服務器響應后,建立的連接會使用HTTP升級從HTTP協議交換為web sockt協議。
只有在支持web socket協議的服務器上才能正常工作。
var socket = new WebSockt('ws://www.baidu.com');//http->ws; https->wss socket.send('hello WebSockt'); socket.onmessage = function(event){ var data = event.data; }
CORS 定義
Cross-Origin Resource Sharing(CORS)跨來源資源共享是一份瀏覽器技術的規范,提供了 Web 服務從不同域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,是 JSONP 模式的現代版。與 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。用 CORS 可以讓網頁設計師用一般的 XMLHttpRequest,這種方式的錯誤處理比 JSONP 要來的好。另一方面,JSONP 可以在不支持 CORS 的老舊瀏覽器上運作。現代的瀏覽器都支持 CORS。
CORS是W3c工作草案,它定義了在跨域訪問資源時瀏覽器和服務器之間如何通信。CORS背后的基本思想是使用自定義的HTTP頭部允許瀏覽器和服務器相互了解對方,從而決定請求或響應成功與否。W3C CORS 工作草案
同源策略:是瀏覽器最核心也最基本的安全功能;同源指的是:同協議,同域名和同端口。精髓:認為自任何站點裝載的信賴內容是不安全的。當被瀏覽器半信半疑的腳本運行在沙箱時,它們應該只被允許訪問來自同一站點的資源,而不是那些來自其它站點可能懷有惡意的資源;參考:JavaScript 的同源策略
JSON & JSONP:JSON 是一種基於文本的數據交換方式,或者叫做數據描述格式。JSONP是資料格式JSON的一種“使用模式”,可以讓網頁從別的網域要資料,由於同源策略,一般來說位於server1.example.com的網頁無法與不是 server1.example.com的服務器溝通,而HTML的script元素是一個例外。利用script元素的這個開放策略,網頁可以得到從其他來源動態產生的JSON資料,而這種使用模式就是所謂的JSONP
CORS 對比 JSONP
都能解決 Ajax直接請求普通文件存在跨域無權限訪問的問題
- JSONP只能實現GET請求,而CORS支持所有類型的HTTP請求
- 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得數據,比起JSONP有更好的錯誤處理
- JSONP主要被老的瀏覽器支持,它們往往不支持CORS,而絕大多數現代瀏覽器都已經支持了CORS
CORS,BROWSER支持情況
數據來源:caniuse.com
IE6,IE7,Opera min 不支持CORS。具體可參看數據來源中的 'show all'
主要用途
- From a browser script perspective: By allowing cross-domain requests, which are subject to tighter controls on the types of data that is exchanged. Cookies, for instance, are blocked unless specifically requested by the XHR author and allowed by the cross-domain web service. This is done to mitigate the risk of data leaks.
- From a web service perspective: By utilising the origin URL reported by the browser the target cross-domain web service can determine, based on its origin policy, whether to allow or deny the request.
Ajax請求跨域資源的異常
當出現如下異常時,那么就需要考慮跨域的問題了
例如 localhost:63343 通過Ajax請求http://192.168.10.61:8080服務器資源時就會出現如下異常:
CORS 實現思路
CORS背后的基本思想是使用自定義的HTTP頭部允許瀏覽器和服務器相互了解對方,從而決定請求或響應成功與否
安全說明
CORS is not about providing server-side security. The Origin request header is produced by the browser and the server has no direct means to verify it.
CORS 並不是為了解決服務端安全問題,而是為了解決如何跨域調用資源。至於如何設計出 安全的開放API,卻是另一個問題了,這里提下一些思路:
- 請求時間有效性(驗證timestamp與服務接到請求的時間相差是否在指定范圍內,比如5分鍾內)
- token驗證
- ip驗證
- 來源驗證
例如
{
'name': 用戶名, ‘key: 加密的驗證key,//(name+secret+timestamp來通過不可逆加密生成) ‘timestamp’: 時間戳,//驗證timestamp與服務接到請求的時間相差是否在指定范圍內,比如5分鍾內 }
支持多域名配置的CORS Filter
因為知道已經有可以用的庫可以解決,所以就沒重復造輪子了。其實因為懶,看看別人的源碼算了。。。
在mvnrepository搜索cors-filter,目前也就兩個可以用
- org.ebaysf.web 的 cors-filter,項目地址:https://github.com/ebay/cors-filter
- com.thetransactioncompany的 cors-filter,項目地址:http://software.dzhuvinov.com/cors-filter.html
這兩個也都大同小異,因為ebay開源在github上,也有詳細的README,那么就以ebay的cors-filter為例
配置
添加依賴包到項目:
<dependency> <groupId>org.ebaysf.web</groupId> <artifactId>cors-filter</artifactId> <version>1.0.1</version> </dependency>
添加配置(具體配置項,還是見項目的README.md吧)
<filter> <filter-name>CORS Filter</filter-name> <filter-class>org.ebaysf.web.cors.CORSFilter</filter-class> <init-param> <param-name>cors.allowed.origins</param-name> <param-value>http://192.168.56.129,http://192.168.56.130</param-value> </init-param> <init-param> <param-name>cors.allowed.methods</param-name> <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value> </init-param> <init-param> <param-name>cors.allowed.headers</param-name> <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value> </init-param> </filter> <filter-mapping> <filter-name>CORS Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
當一個資源從與該資源本身所在的服務器不同的域或端口不同的域或不同的端口請求一個資源時,資源會發起一個跨域 HTTP 請求。
出於安全考慮,瀏覽器會限制從腳本內發起的跨域HTTP請求。跨域資源共享機制允許 Web 應用服務器進行跨域訪問控制,從而使跨域數據傳輸得以安全進行。瀏覽器支持在 API 容器中使用 CORS,以降低跨域 HTTP 請求所帶來的風險。
針對於JAVA開發而言,為了更好的做業務分層,經常會將前后端代碼分離開來,發布在不同的服務器上,此時,便會遇到跨域的問題。
跨域有很多種解決方案,如果你在使用SpringMVC來開發服務器的話,這個文章會對你有所幫助。它定義了一個Filter來實現跨域請求的問題,實現從不同的服務器上獲取HTTP請求資源。
代碼示例如下:
## 1. 添加一個JAR包,來實現CORS功能:
gradle: 'com.thetransactioncompany:cors-filter:2.5'
## 2. 在項目的web.xml文件下,添加一個Filter標簽:
<!--CORS 跨域資源訪問--> <filter> <filter-name>CORS</filter-name> <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class> <init-param> <param-name>cors.allowGenericHttpRequests</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>cors.allowOrigin</param-name> <param-value>*</param-value> </init-param> <init-param> <param-name>cors.allowSubdomains</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>cors.supportedMethods</param-name> <param-value>GET, HEAD, POST, OPTIONS</param-value> </init-param> <init-param> <param-name>cors.supportedHeaders</param-name> <param-value>*</param-value> </init-param> <init-param> <param-name>cors.exposedHeaders</param-name> <param-value>X-Test-1, X-Test-2</param-value> </init-param> <init-param> <param-name>cors.supportsCredentials</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>cors.maxAge</param-name> <param-value>3600</param-value> </init-param> </filter> <filter-mapping> <filter-name>CORS</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
## 3. 重啟你的服務,就可以實現跨域請求了。