什么是跨域及怎么解決跨域問題?


什么是跨域?

這篇博文解釋的挺清楚,我直接引用 https://blog.csdn.net/lambert310/article/details/51683775

跨域,指的是瀏覽器不能執行其他網站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器施加的安全限制。

所謂同源是指,域名,協議,端口均相同,只要有一個不同,就是跨域。不明白沒關系,舉個栗子:

http://www.123.com/index.html 調用 http://www.123.com/server.php (非跨域)

http://www.123.com/index.html 調用 http://www.456.com/server.php (主域名不同:123/456,跨域)

http://abc.123.com/index.html 調用 http://def.123.com/server.php (子域名不同:abc/def,跨域)

http://www.123.com:8080/index.html 調用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)

http://www.123.com/index.html 調用 https://www.123.com/server.php (協議不同:http/https,跨域)

請注意:localhost和127.0.0.1雖然都指向本機,但也屬於跨域。

瀏覽器執行javascript腳本時,會檢查這個腳本屬於哪個頁面,如果不是同源頁面,就不會被執行。

 

跨域會阻止什么操作?

瀏覽器是從兩個方面去做這個同源策略的,一是針對接口的請求,二是針對Dom的查詢

1.阻止接口請求比較好理解,比如用ajax從http://192.168.100.150:8020/實驗/jsonp.html頁面向http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp發起請求,由於兩個url端口不同,所以屬於跨域,在console打印台會報No 'Access-Control-Allow-Origin' header is present on the requested resource

 

值得說的是雖然瀏覽器禁止用戶對請求返回數據的顯示和操作,但瀏覽器確實是去請求了,如果服務器沒有做限制的話會返回數據的,在調試模式的network中可以看到返回狀態為200,且可看到返回數據

 

 

2.阻止dom獲取和操作

關於iframe中對象的獲取方式請看:https://blog.csdn.net/lianzhang861/article/details/84870484

比如a頁面中嵌入了iframe,src為不同源的b頁面,則在a中無法操作b中的dom,也沒有辦法改變b中dom中的css樣式。

而如果ab是同源的話是可以獲取並操作的。

<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
iframe{
width:100%;height:800px;
}
</style>
</head>
<body>
<!--<iframe src="http://192.168.100.150:8081/zhxZone/webmana/attachment/imageManager" frameborder="0" id="iframe"></iframe>-->
<iframe src="http://192.168.100.150:8020/實驗/jsonp.html" frameborder="0" id="iframe"></iframe>
<script type="text/javascript">
var i=document.getElementById("iframe");
i.onload=function(){
/*console.log(i.contentDocument)
console.log(i.contentWindow.document.getElementById("text").innerHTML)*/
var b=i.contentWindow.document.getElementsByTagName("body")[0];
i.contentWindow.document.getElementById("text").style.background="gray";
i.contentWindow.document.getElementById("text").innerHTML="111";
}
</script>
</body>
</html>
改變了iframe中的元素 

 

甚至是可以獲取iframe中的cookie

var i=document.getElementById("iframe");
i.onload=function(){
console.log(i.contentDocument.cookie)
}


不用說也知道這是極為危險的,所以瀏覽器才會阻止非同源操作dom

瀏覽器的這個限制雖然不能保證完全安全,但是會增加攻擊的困難性

雖然安全機制挺好,可以抵御壞人入侵,但有時我們自己需要跨域請求接口數據或者操作自己的dom,也被瀏覽器阻止了,所以就需要跨域

跨域的前提肯定是你和服務器是一伙的,你可以控制服務器返回的數據,否則跨域是無法完成的 

 

解決跨域的方法:
1.前端方法就用jsonp

jsonp是前端解決跨域最實用的方法

原理就是html中 的link,href,src屬性都是不受跨域影響的,link可以調用遠程的css文件,href可以鏈接到隨便的url上,圖片的src可以隨意引用圖片,script的src屬性可以隨意引入不同源的js文件

看下面代碼,a.html頁面中有一個func1方法,打印參數ret

<body>
<script type="text/javascript">
function func1(ret){
console.log(ret)
}
</script>
<script src="http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp.js" type="text/javascript" charset="utf-8"></script>
</body>
而引入的jsonp.js中的代碼為:

func1(111)
可想而知結果會打印出 111,也就是說a頁面獲取到了jsonp.js中的數據,數據是以調用方法並將數據放到參數中返回來的

但是這樣獲取數據,必須a.html中的方法名與js中的引用方法名相同,這樣就是麻煩很多,最好是a.html能將方法名動態的傳給后台,后台返回的引入方法名就用我傳給后台的方法名,這樣就做到了由前台控制方法名

總之要做到的就是前台像正常調接口一樣,后台要返回回來js代碼即可

現在改為動態方法名:我請求的接口傳入callback參數,值為方法名func1

<body>
<script type="text/javascript">
function func1(ret){
console.log(ret)
}

</script>
<script src="http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp?callback=func1" type="text/javascript" charset="utf-8"></script>
</body>
后台返回不同編程語言不同,我使用的是java,所以我展示一下java返回的方法

//jsonp測試
@ResponseBody
@RequestMapping(value = "jsonp", produces = "text/plain;charset=UTF-8")
public void jsonp(String callback, HttpServletRequest req, HttpServletResponse res) {
List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
RetBase ret=new RetBase();
try {
res.setContentType("text/plain");
res.setHeader("Pragma", "No-cache");
res.setHeader("Cache-Control", "no-cache");
res.setDateHeader("Expires", 0);
Map<String,Object> params = new HashMap<String,Object>();
list = dictService.getDictList(params);
ret.setData(list);
ret.setSuccess(true);
ret.setMsg("獲取成功");
PrintWriter out = res.getWriter();
//JSONObject resultJSON = JSONObject.fromObject(ret); //根據需要拼裝json
out.println(callback+"("+JSON.toJSONString(ret)+")");//返回jsonp格式數據
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
上述java代碼相當於我返回了 " func1(數據) "的代碼,所以返回數據成功打印,完成了跨域請求

 

到這里,每次請求數據還要引入一個js才行,代碼有些雜亂,前端可以繼續優化代碼,動態的生成script標簽

<script type="text/javascript">
function func1(ret){
console.log(ret)
}
var url="http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp?callback=func1";
var s=document.createElement("script");
s.setAttribute("src",url);
document.getElementsByTagName("head")[0].appendChild(s);

</script>
 這樣,原生的jsonp跨域就基本完成了,但是用起來不是很方便

我推薦使用jquery封裝的jsonp,使用起來相對簡單

使用起來跟使用ajax類似,只是dataType變成了jsonp,且增加了jsonp參數,參數就是上述的callback參數,不需要管他是啥值,因為jq自動給你起了個名字傳到后台,並自動幫你生成回調函數並把數據取出來供success屬性方法來調用

jq jsonp標准寫法:

$.ajax({
type: 'get',
url: "http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp",
dataType: 'jsonp',
jsonp:"callback",
async:true,
data:{

},
success: function(ret){
console.log(ret)
},
error:function(data) {
},
});
jq jsonp的簡便寫法:

$.getJSON("http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp?callback=?",function(ret){
console.log(ret)
})
后台接收到的callback參數,jq自己起的名字

 

這樣使用起來就跟ajax一樣順手了,把返回的值在success中操作即可,只不過是可以跨域了

這里針對ajax與jsonp的異同再做一些補充說明:

1、ajax和jsonp這兩種技術在調用方式上”看起來”很像,目的也一樣,都是請求一個url,然后把服務器返回的數據進行處理,因此jquery框架都把jsonp作為ajax的一種形式進行了封裝。

2、但ajax和jsonp其實本質上是不同的東西。ajax的核心是通過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態添加

 

2.后台配置解決跨域

  要說前端解決跨域用jsonp最好,但我更喜歡通過配置后台設置

同樣,因為我用的java,所有我只能列舉java的配置方法

我用的是 maven,spring mvc

首先在pom.xml中引入依賴

<!--跨域依賴-->
<dependency>
<groupId>com.thetransactioncompany</groupId>
<artifactId>cors-filter</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>com.thetransactioncompany</groupId>
<artifactId>java-property-utils</artifactId>
<version>1.9</version>
</dependency>
<dependency>
然后在web.xml配置過濾器

<!--為了允許跨域訪問-->
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
這樣前端就可以盡情跨域請求數據了,是不是很方便?

注意如果項目中配置了檢測是否登錄過濾器,可能會起沖突,因為沒有登錄的話每次都會跳轉到登錄接口。。。

 

下面再列舉一些解決跨域方法,這些跨域方法有局限性也有特殊場景用途,應該了解一下

3.通過修改document.domain來跨子域

此方法有介紹價值,因為關系到操作dom方面的跨域

上述方法都只能解決請求跨域,而無法解決跨域操作dom,因為想操作dom條件比較苛刻,必須這兩個域名必須屬於同一個基礎域名!而且所用的協議,端口都要一致

這個在這篇文章里有介紹 https://segmentfault.com/a/1190000005863659

@(StuRep)

document.domain
用來得到當前網頁的域名。
比如在地址欄里輸入:

代碼如下:
javascript:alert(document.domain); //www.jb51.net
我們也可以給document.domain屬性賦值,不過是有限制的,你只能賦成當前的域名或者基礎域名。
比如:

代碼如下:
javascript:alert(document.domain = "jb51.net"); //jb51.net
javascript:alert(document.domain = "www.jb51.net"); //www.jb51.net
上面的賦值都是成功的,因為www.jb51.net是當前的域名,而jb51.net是基礎域名。
但是下面的賦值就會出來"參數無效"的錯誤:

代碼如下:
javascript:alert(document.domain = "cctv.net"); //參數無效
javascript:alert(document.domain = "www.jb51.net"); //參數無效
因為cctv.net與www.jb51.net不是當前的域名也不是當前域名的基礎域名,所以會有錯誤出現。這是為了防止有人惡意修改document.domain來實現跨域偷取數據。

利用document.domain 實現跨域:
前提條件:這兩個域名必須屬於同一個基礎域名!而且所用的協議,端口都要一致,否則無法利用document.domain進行跨域.
Javascript出於對安全性的考慮,而禁止兩個或者多個不同域的頁面進行互相操作。
相同域的頁面在相互操作的時候不會有任何問題。

比如在:aaa.com的一個網頁(a.html)里面 利用iframe引入了一個bbb.com里的一個網頁(b.html)。
這時在a.html里面可以看到b.html里的內容,但是卻不能利用javascript來操作它。因為這兩個頁面屬於不同的域,在操作之前,js會檢測兩個頁面的域是否相等,如果相等,就允許其操作,如果不相等,就會拒絕操作。
這里不可能把a.html與b.html利用JS改成同一個域的。因為它們的基礎域名不相等。(強制用JS將它們改成相等的域的話會報跟上面一樣的"參數無效錯誤。")

所以如果在a.html里引入aaa.com里的另一個網頁,是不會有這個問題的,因為域相等。
有另一種情況,兩個子域名:
aaa.xxx.com
bbb.xxx.com
aaa里的一個網頁(a.html)引入了bbb 里的一個網頁(b.html),
這時a.html里同樣是不能操作b.html里面的內容的。
因為document.domain不一樣,一個是aaa.xxx.com,另一個是bbb.xxx.com。
這時我們就可以通過Javascript,將兩個頁面的domain改成一樣的,
需要在a.html里與b.html里都加入:

代碼如下:
document.domain = "xxx.com";
這樣這兩個頁面就可以互相操作了。也就是實現了同一基礎域名之間的"跨域"。

4.通過window.name跨域

https://blog.csdn.net/u013558749/article/details/56968333

5.通過HTML5中新引進的window.postMessage方法來跨域傳送數據

這個跨域主要是用於多iframe窗口之間消息傳遞或者父窗口與iframe傳遞消息的,屬於比較狹義的跨域。比如在a界面修改內容,點擊保存后b頁面的表格自動刷新就可以使用這個。或者子iframe做了事件,父在跨域的情況下無法獲取子的事件,但通過消息傳遞就可以間接獲取到事件。

前提:跨域和被跨域的一方都是你可以控制的,一方寫發送消息的,另一方寫接收消息方法

注意這跨域的局限性在於必須在同一個window對象上,也就是說哪個window發送消息,只有本window才能接收到。

主要語法:

發送message

window.postMessage(message, targetOrigin);
監聽並接收message

window.parent.addEventListener("message",function(res){
console.log(res)
})
詳細介紹看這個 https://blog.csdn.net/lianzhang861/article/details/100031166

這里面詳細介紹了應用場景和注意事項

6.通過 CORS解決跨域

CORS背后的基本思想是使用自定義的HTTP頭部允許瀏覽器和服務器相互了解對方,從而決定請求或響應成功與否.

*這其實和第2中方法(后台配置)基本相同,都是通過過濾器在response中返回頭部,使服務器和瀏覽器可互通

Access-Control-Allow-Origin:指定授權訪問的域
Access-Control-Allow-Methods:授權請求的方法(GET, POST, PUT, DELETE,OPTIONS等)

適合設置單一的(或全部)授權訪問域,所有配置都是固定的,特簡單。也沒根據請求的類型做不同的處理

依舊是列舉java中的方法。。。

首先自己寫一個過濾器CORSFilter

package com.xxx.common.filter;

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* Created by 12143 on 2018/12/7.
*/
@Component
public class CORSFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.addHeader("Access-Control-Allow-Origin", "http://192.168.100.150:8020");
//response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.addHeader("Access-Control-Allow-Headers", "Content-Type");
response.addHeader("Access-Control-Max-Age", "1800");//30 min
filterChain.doFilter(request, response);
}
}
注意:Access-Control-Allow-Origin為*則允許所有url訪問,如果為“http://192.168.100.150:8020”則只有此url才能訪問,注意有端口的要把端口也寫上
比如配置了http://192.168.56.130:8081,那么只有http://192.168.56.130:8081 能拿到數據,否則全部報403異常

然后在web.xml中添加此過濾器

<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.xxx.common.filter.CORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
完成

7.通過Nginx反向代理 

上個方法跨域是借助了瀏覽器對 Access-Control-Allow-Origin 的支持。但有些瀏覽器是不支持的,所以這並非是最佳方案,現在我們來利用nginx 通過反向代理 滿足瀏覽器的同源策略實現跨域! 

這里是一個nginx啟用COSR的參考配置

#
# Wide-open CORS config for nginx
#
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
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-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-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';
}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM