转载出处:https://blog.csdn.net/lianzhang861/article/details/84871369
什么是跨域?
这篇博文解释的挺清楚,我直接引用 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.οnlοad=
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.οnlοad=
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传递消息的,属于比较狭义的跨域。比如在a界面修改内容,点击保存后b页面的表格自动刷新就可以使用这个。
主要语法:
发送message
window.postMessage(message, targetOrigin, [transfer]);
监听并接收message
-
window.addEventListener(
"message",
function(MessageEvent){
-
var origin = event.origin || event.originalEvent.origin;
-
....
-
},
false);
详细介绍看看这个 https://www.cnblogs.com/dolphinX/p/3464056.html
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';
-
}
-
}