转载于:https://www.kancloud.cn/codepan/ajax-cross-domain/646545
跨域问题
一、什么是跨域
- 前后端不在同一个域就会存在跨域问题。
- 而现在跨域问题越来越常见,因为目前很多团队都在采用前后端分离的这种模式进行开发的,前端和后端的代码部署在不同的服务器上。
二、跨域产生的原因
产生跨域的“罪魁祸首”只有一个:那就是浏览器,具体说是浏览器的“同源策略”。
浏览器的同源策略用于隔离潜在恶意文件的重要安全机制。
跨域产生的原因是下面三点:
1、浏览器的限制
2、跨域
3、请求是XHR(XMLHttpRequest)
对于第一个原因:很多人认为,发生跨域问题是服务器的行为:服务器不允许浏览器调用,其实不然,真正的原因是浏览器处于安全的考虑,当发现发出的请求是跨域请求时,就会做一些安全的校验,如果校验未通过,则就会报跨域安全问题。说白了,就是浏览器“多管闲事”,不是服务器不允许浏览器调用。
验证这种说法的方法是:
第一步:后端会有日志输出,说明后端正确收到请求了。
第二步:请求的状态码也是200表示请求成功,后端已经成功响应请求结果了,前端也成功获取了响应>结果。
第三步:上面两部一切正常,但是浏览器控制台报错了,报告跨域安全问题。
对于第二个原因:请求不跨域,就会产生跨域的问题
对于第三个原因:也就是说如果发送的请求不是XHR请求,则就不会出现跨域安全问题,就算是跨域 请求,浏览器也不会报错。
浏览器发送请求的途径有:
代码如下(示例):
1. <img src="">
2. <script src="">
验证方法是:使用img标签或者script标签的src属性访问接口,然后看浏览器控制台是不会报跨域安全错误的。
三、解决思路和方法
解决思路是针对产生跨域安全问题的三点原因进行一一攻破即可。
-
首先第一点 “浏览器的限制”
那么只要禁止掉浏览器的限制就OK。启动浏览器时通过给其指定参数可以禁止掉跨域限制的功能,但是其实这样做意义不大,因为不可能给每个使用你系统的用户电脑上的浏览器去加这个参数吧。。。。 -
其次第二点 “请求是XHR类型”
那么只要发送的请求不是XHR的就行。于是出现了大名鼎鼎的JSONP。 -
最后第三点 “跨域”
JSONP虽然能够解决问题,但是它存在很多弊端,导致现在JSONP使用的越来越少了。所以我们解决跨域安全问题的重点就落在了第三点上-即解决“跨域”。
解决跨域有两种思路:
1、在后端解决(支持跨域):后端在响应头里面加入一些允许浏览器跨域的信息,用来告诉浏览器:“我允许XXX客户端访问我,你就不要做跨域安全的检查了”。
2、在前端解决(隐藏跨域):通过代理来解决。比如之前A访问C存在跨域,现在可以加一个B服务器,让A先访问B,B再去访问C,A和B是同域名的
总结:
jsonp
nginx 反向代理(nginx 服务内部配置 Access-Control-Allow-Origin *)
CORS 前后端协作设置请求头部,Access-Control-Allow-Origin 等头部信息
iframe 嵌套通讯,postmessage
1、禁止浏览器检查
命令行参数启动
chrome --disable-web-security --user-data-dir=g:\temp3
mac
open -n /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir=/Users/didi/Desktop/chromeData
2、发送非XHR请求-JSONP解决方案
JSONP是什么?
JSON For Padding JSON的补充。其实本质和JSON没有半毛钱关系。使用script标签。它是变通的一种解决方案。
使用JSONP服务器代码需要改动吗?
答案是:不改动,别想成功。
JSONP的实现原理
与普通XHR请求不同的地方有:
- 请求的Type
请求的type类型是script,不是xhr - response的Content-Type
响应的数据不是json对象,而是一段js脚本。也就是response的Content-Type是application/javascript,不是application/json - URL
JSONP的URL后面自动加入了一长串参数,其中有一个参数是callback=XXX,这个callback的值就是将来接口返回的JS脚本的函数名。也就是JS脚本其实就是对XXX函数的调用,而真正的JSON数据被当做函数的参数被传回
$.ajax({
url: 'api/getUser.do',
dataType: 'jsonp', // 响应的数据类型是jsonp形式
jsonp: 'callback', // 指定jsonp前后端约定的回调函数的名称,默认是callback
success: function (json) {
}
})
JSONP有什么弊端?
- 服务器需要改动代码来支持
- 只支持GET 因为底层是script的src,所以只能是GET
- 发送的不是XHR请求 XHR会有异步、事件等众多特性
3、允许跨域
被调用方解决
宗旨:支持跨域
实现位置:
- 服务器端(应用服务器)(tomcat、jetty、weblogic)
- nginx配置
- apache配置
重点是服务器端的实现
cors
应用服务器处的Filter解决方案
- 浏览器是先执行还是先判断?
先执行后判断 - 如何判断?
跨域请求的请求头中多了一个Origin字段,这个字段的值是当前的域名信息。也就说:浏览器发现本次请求是跨域请求时,就会在请求头中增加一个Origin字段,然后等待服务器响应返回之后,判断响应头中有没有允许Origin跨域的信息,如果有则OK,如果没有则就会报错。
在doFilter方法中加入以下代码:
HttpServletResponse res = (HttpServletResponse) response;
res.addHeader("Access-Control-Allow-Origin", "http://localhost:8080");
res.addHeader("Access-Control-Allow-Methods", "GET");
chain.doFilter(request, response);
以上代码只允许一个域名的跨域,只允许GET请求,如果想允许多个域名跨域,多个请求方法,代码修改为:
res.addHeader("Access-Control-Allow-Origin", "*");
res.addHeader("Access-Control-Allow-Methods", "*");
思考:Origin设置为,是不是可以满足所有的场景呢?
简单请求和非简单请求
针对 “浏览器是先执行还是先判断?”的问题,答案是:
浏览器发送跨域请求时,会判断请求是否是简单请求。如果是简单请求,则会先执行,后判断;如果是非简单请求,则会先判断(发送一个预检命令,检查通过后,才会发送真正的请求),后请求。
简单请求
- 1、request methods为以下几种:
get head post - 2、equest header里面
- 无自定义头
- Content-Type为以下几种:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
非简单请求
- 1、request methods为以下几种:
put delete - 2、request header里面
- 有自定义头
- Content-Type为
- application/json
- 有自定义头
OPTIONS预检命令
request methods是OPTIONS。是在发送Content-Type=application/json形式的请求之前发送的一个命令。
发送OPTIONS请求时,浏览器会报如下错误:
Request header field Content-Type is not allowed by Access-Control-Headers in preflight response.
错误的意思是:发送OPTIONS请求时请求头里面会有一个Access-Control-Request-Headers字段,字段值是content-type,但是响应头中没有允许。所以解决办法就是在Filter中增加一个响应头。
res.addHeader("Access-Control-Allow-Headers", "Content-Type");
OPTIONS预检命令的缓存
发送json形式的请求时,每次都会发送两次请求:一次OPTIONS请求,一次真正的业务请求。这样非常浪费资源,影响效率。解决问题的办法是:可以增加一个响应头,缓存预检命令。
res.addHeader("Access-Control-Max-Age", "3600");
意思是:告诉浏览器,在3600秒之内缓存请求信息,不需要多次发送预检命令
带有Cookie的跨域
http中的回话,也就是session,是依赖于cookie来实现的,sessionId存放在cookie中
在之前的讲解中提到:“Access-Control-Allow-Origin”:“”?这句代码中的能满足所有的使用场景吗?答案是:NO!NO!NO!
发送带有cookie的跨域请求时:
- Access-Control-Allow-Origin需要全匹配,不能是*号只能是本域
- 需要添加res.addHeader(“Access-Control-Allow-Credentials”, “true”)
- 前端以jquery为例
$.ajax({
type: 'get',
url: 'getCookie.do',
xhrFields: {
withCredentials: true
},
success (json) {
}
})
由于现在Origin是全匹配,那么也就是只能允许一个域名的跨域调用,如果其他域名也要可跨域调用,此时该如何解决?
解决办法是:可以获取到request headers中的Origin字段,然后动态的写入Origin即可
String origin = req.getHeader("Origin");
if (!StringUtils.isEmpty(origin)) {
res.addHeader("Access-Control-Allow-Origin", origin);
}
带自定义头的跨域
前端:
$.ajax({
type: 'get',
url: 'getHeader.do',
headers: { // 添加自定义头方法1
'x-header1': 'AAA',
},
beforeSend (xhr) { // 添加自定义头方法2
xhr.setRequestHeader('x-header2', 'BBB')
},
success (json) {
}
})
后端:
String headers= req.getHeader("Access-Control-Allow-Headers");
if (!StringUtils.isEmpty(headers)) {
res.addHeader("Access-Control-Allow-Headers", headers );
}
nginx解决方案
上一节中的前后端交互是:前端->应用服务器(tomcat,jetty,weblogic)
而真正的流程是:前端->http服务器(apache、nginx)->应用服务器(tomcat,jetty,weblogic)->http服务器(apache、nginx)->前端,
中间多了一层http服务器的中转。
这样一来,就会有两个地方可以增加响应头,实现跨域:
- 应用服务器(上一节的Filter解决方案就是在这里增加的响应头)
- http服务器(这里是本节需要研究的问题
什么是虚拟主机?
关键是“虚拟”二字。虚拟主机的意思是:多个域名指向同一个服务器,服务器根据不同的域名,把请求转到不同的应用服务器,看上去好像有多个主机,实际只有一台,这就是虚拟主机。
nginx的配置步骤:
1、进行host配置,在hosts文件中进行如下配置:
127.0.0.1 b.com
2、进行nginx配置,打开nginx目录,进入conf目录,新建vhost目录(用来存放虚拟主机的配置文件), 打开nginx.conf文件,在最后加入如下代码:
include vhost/*.conf;
代码的意思是导入vhost目录下的所有配置文件。
然后在vhost目录下新建b.com.conf文件
server{
listen 80;
server_name b.com;
location /{
proxy_pass http://locahost:8080/;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Orgin $http_origin;
add_header Access-Control-Allow-Headers $http_access_control_request_headers;
if ($request_method === OPTIONS) {
return 200;
}
}
}
apache解决方案
。。。。。。
调用方解决
iframe 嵌套通讯,postmessage