AJAX跨域问题的全面解读

1.简介

通过本文了解是跨域问题,从系统最常见的部署结构上分析跨域解决的思路,详细讲解jsonp的工作机制,http协议如何支持跨域,以及http服务器nginx和apache的2种不同解决思路,让大家知其然并知其所以然,快速掌握问题本质和分析问题的方法。

2.什么是跨域

​ JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。那什么是跨域呢,简单地理解就是因为JavaScript同源策略的限制,a.com域名下的js无法操作b.com或是c.a.com域名下的对象。
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。

1567650614321

​ 有一点必须要注意:跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。

2.跨域产生的原因

  1. 浏览器限制。如果浏览器发现请求是跨域的时候,就会做校验,如果校验不通过就会报跨域的错误
  2. 发送的XHR(XMLHTTPRequest)请求。如果发送的不是xhr请求,无论是否跨域,浏览器都不会报错

只有这三种原因同时满足时,才会发生跨域

3.解决跨域的思路

  1. 浏览器角度。

    让浏览器不做校验,浏览器的设置跨域,在浏览器的属性设置页面的目标输入框加上–disable-web-security。这样浏览器将不会去做校验了。但是每个人都需要去改动,==不建议使用。==

  2. 发送xhr请求角度。

    让跨域的请求不发送xhr请求,就不会再报错了。办法是使用jsonp。

    jsonp是非官方协议,是前后端的一种约定,前端使用ajax发送请求,dataType为jsonp,并且带一个参数(默认是callback),当后台发现这个参数之后,就知道这是一个jsonp请求,就会把原本返回json对象编程js返回,js代码是一个函数的执行,函数名是callback的参数值,函数的参数是原本的json对象。

    jsonp缺点:

    • 1).只支持get方法请求;
    • 2).==需要服务器修改代码;==JSONP需要后台配合, 要有一个参数, 默认是?callback=JQuery1234567… JSONP返回的类型是:javascript, 而非json
    • 3).发送的不是XHR请求(这也是它可以跨域的原因),没有回调及事件的特性
  3. 在跨域角度。

    • 1).被调用方解决:在响应头增加指定字段,告诉浏览器,允许调用,这种方法的原理是从根源支持跨域的。
    • 2).调用方解决:这种解决办法原理是隐藏跨域。使用代理,在同一个域请求不同的url地址,转发到不同的域。

4.具体解决跨域问题的方法

4.1 跨越问题的现象

通过浏览器访问A项目,项目中打开的页面中将通过JavaScript代码访问B项目的地址。出现跨域问题。

A项目Javascript代码(调用方代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<head>
<script src="jquery-1.11.3.js"></script>
<link rel="stylesheet" type="text/css" href="jasmine-2.8.0/jasmine.css">
<script src="jasmine-2.8.0/jasmine.js"></script>
<script src="jasmine-2.8.0/jasmine-html.js"></script>
<script src="jasmine-2.8.0/boot.js"></script>
</head>
<body>

<script>
function get1() {
$.getJSON("http://localhost:8080/test/get1").then(function(result) {
console.log(result);
});
}

// 每一个测试用例的超时时间
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;

// 请求的接口的前缀 // http://localhost:8080/test
//var base = "/ajaxserverapache";
var base = "http://localhost:8080/test";
//测试模块
describe("晓风轻-ajax跨越完全讲解", function() {
// 测试方法
it("get1请求", function(done) {
// 服务器返回的结果
var result;

$.getJSON(base + "/get1").then(function(jsonObj) {
result = jsonObj;
});

// 由于是异步请求,需要使用setTimeout来校验
setTimeout(function() {
expect(result).toEqual({
"data" : "get1 ok"
});

// 校验完成,通知jasmine框架
done();
}, 100);
});

正常访问,服务端返回响应码为200,但是页面会报错。

1567675851094

浏览器的Console中会打印报错信息:

1567676338007

4.2 基于Jsonp 解决

4.2.1 前端代码改动

​ 在发送ajax请求的时候,设置dataType为jsonp,即告诉后台我需要返回的是script代码,而不是一个json格式的数据。同时==jsonp:“callback”(默认)==,但是可以修改为jsonp:“callback2”这个就是前后台的协议,即后台返回的script的方法的名称,然后后台将原本的json放在该方法的参数中返回给前端,故服务端代码应该也以相同名称返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 测试方法
it("jsonp请求", function(done) {
// 服务器返回的结果
var result;
$.ajax({
url: base +"/get1",
dataType: "jsonp",
jsonp: "callback2",
cache:true,
success: function(json){
result = json;
}
});

此时会发生两种变化:

  • 1请求的地址发生改变
  • 2发起请求的类型不在为xhr,而是script。返回的数据类型也为 application/javascript

1567676719744

4.2.2 服务端代码改动

服务端需要新增@ControllerAdvice 修饰的方法。默认请求的传递参数为 callback。如果客户端javascript代码中以jsonp:“callback2”访问服务端,服务端也应该以callback2 相同的字符串返回。

1
2
3
4
5
6
7
8
9
10
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

public JsonpAdvice() {
super("callback2");
}
}

返回内容为javascript内容:

1567676920098

4.2.3 核心原理

引入jquery非压缩的js库,可以在浏览器查看JQuery实现jsonp的时候是动态创建异步javascript的代码。

1567677458922

在浏览器查看jsonp请求里除了callback参数外,还有一个名为下划线“_”的参数,值为一串随机数。此参数作用是防止请求被缓存。如果你的请求可以被缓存的话,可以在请求里使用cache:true。

4.3 常见的JAVAEE 架构中的解决跨域问题的方案

4.3.1 跨域解决方向

  • 被调用方解决

  • 调用方解决

1567677890052

4.3.2 被调用方解决-支持跨域

根据http协议关于跨域方面的要求,增加响应头信息,告诉浏览器允许被跨域调用

4.3.2.1 使用filter解决

4.3.2.1.2 简单请求与与非简单请求
  • 简单请求:先执行后判断

  • 非简单请求:当浏览器要发送跨域请求时,如果请求是复杂请求,浏览器会先发送一个options预检命令即一个options请求,当该请求通过时才会再发送真正的请求。

两种请求的区分:

1567678862767

如下为非简单请求

该option请求会根据请求的信息去询问服务端支不支持该请求。比如发送的数据是json类型(通过content-type设置)的话,会携带一个请求头Access-Control-Request-Headers: content-type去询问支不支持该数据类型,如果支持,则请求就会通过,并发送真正的请求。

img

1567679422863

1567679765075

4.3.2.1.3 非简单请求跨域基于Filter解决办法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
public class AjaxserverApplication {

public static void main(String[] args) {
SpringApplication.run(AjaxserverApplication.class, args);
}

@Bean
public FilterRegistrationBean registerFilter() {

FilterRegistrationBean bean = new FilterRegistrationBean();

bean.addUrlPatterns("/*");
bean.setFilter(new CrosFilter());

return bean ;
}
}

自定义Filter(同时解决带cookie的跨域、以及自定义请求头)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.tomcat.util.buf.StringUtils;

public class CrosFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// TODO Auto-generated method stub

HttpServletResponse res = (HttpServletResponse) response;

HttpServletRequest req = (HttpServletRequest) request;

String origin = req.getHeader("Origin");

if (!org.springframework.util.StringUtils.isEmpty(origin)) {
//带cookie的时候,origin必须是全匹配,不能使用*
res.addHeader("Access-Control-Allow-Origin", origin);
}

res.addHeader("Access-Control-Allow-Methods", "*");

String headers = req.getHeader("Access-Control-Request-Headers");

// 支持所有自定义头
if (!org.springframework.util.StringUtils.isEmpty(headers)) {
res.addHeader("Access-Control-Allow-Headers", headers);
}

res.addHeader("Access-Control-Max-Age", "3600");
// enable cookie
res.addHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(request, response);
}

@Override
public void destroy() {
// TODO Auto-generated method stub
}

}

4.3.2.2 nginx解决方案

在被调用方的代理服务器nginx 上增加请求头信息

1567680090851

img

4.3.2.3 APACHE解决方案、

在被调用方的代理服务器apache上增加请求头信息

img

4.3.2.4 Spring解决方案

在Controller层增加@CrossOrigin注解即可

img

4.3.3. 调用方使用代理做调用解决跨域问题-隐藏跨域

4.3.3.1 基于nginx使用反向代理

反向代理:访问同一个域名的两个url,会去到两个不同的服务器. javascript代码中的ajax请求地址也应该为相对地址

1567680395410

4.3.3.2 基于apache使用反向代理

img

5.参考

慕课网 https://www.imooc.com/learn/947

6.欢迎关注米宝窝,持续更新中,谢谢!

[米宝窝 https://rocklei123.github.io/ ](https://rocklei123.github.io/)

-------------本文结束感谢您的阅读-------------
欢迎持续关注米宝窝,定期更新谢谢! https://rocklei123.github.io/
欢迎持续关注我的CSDN https://blog.csdn.net/rocklei123
rocklei123的技术点滴
熬夜写博客挺辛苦的,生怕猝死,所以每当写博客都带着听诊器,心脏一有异响,随时按Ctrl+S。
rocklei123 微信支付

微信支付

rocklei123 支付宝

支付宝