Ajax跨域问题

背景

我自己现在正在做的项目采用的是前后端分离的架构,前端是react,生成静态文件部署在CDN中.后端是nginx+tomcat的标准java web项目.在java web项目中只需要放一个index.html就可以了.由于java端的接口已经预先发布上线,前端开发过程中直接使用线上API进行调试,所以就碰到了跨域问题.

插播

为了测试跨域问题,本机最好先安装一个nginx.mac上安装nginx当然首推homebrew.不过由于速度太慢,建议换成国内的源.比如中科大.可以用brew config查看是否设置正确.然后直接用

1
brew install nginx

homebrew装出来的都在/usr/local/目录中,nginx配置文件在:/usr/local/etc/nginx/nginx.conf,静态文件在/usr/local/var/www,安装完nginx后我们继续

模拟

正式开始模拟前最好先熟悉一下基本概念,阮一峰有篇blog讲的非常详细也很易懂.不太明白的同学先看看这里.下面我们开始模拟,首先需要有两个不同domain的应用.假设我们从A应用(localhost)用ajax跨域去请求B应用(siteb.com)的资源.为了看起来比较真实,我把siteb.com映射到了127.0.0.1.同时起了一个普通的java web当做A应用,index.html的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<script language="JavaScript">
$.ajax({
url: "http://siteb.com:9000/index.html",
success: function (result) {
console.log(result);
}

});
</script>
</head>
<body>
<h1>cros test</h1>
</body>
</html>

这时候浏览器会报一个错误:

XMLHttpRequest cannot load http://siteb.com:9000/index.html. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8080‘ is therefore not allowed access.

原因就是nginx上的B应用(siteb.com)在http的response头中没有设置Access-Control-Allow-Origin.
加上配置再试试

1
2
3
4
5
6
7
location / {
root html;
index index.html index.htm;
add_header Set-Cookie "test=1024;";
add_header 'Access-Control-Allow-Origin' '*';
}

果然就好了.这样就搞定了?这只是搞定了最简单的情况,接下来我们再看一下稍微复杂一点的情况.

麻烦点的

上面举的例子是不需要发送cookie的,不过很多场景都是需要的.所以再看看发送cookie的场景是怎么支持的.默认是不发送的.需要修改一下jquery的ajax配置

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
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<script language="JavaScript">
//加上这个设置就能发送cookie了
$.ajaxSetup({
xhrFields: {
withCredentials: true
},
crossDomain: true
});

$.ajax({
url: "http://siteb.com:9000/index.html",
success: function (result) {
console.log("success");
$("#mydiv").html(result);
}

});
</script>
</head>
<body>

<h1>cros test</h1>
<hr>
<div id="mydiv">loading...</div>

</body>
</html>

同时需要在siteb的nginx设置中加上

1
2
3
4
5
6
7
8
location / {
root html;
index index.html index.htm;
add_header Set-Cookie "test=1024;";
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
}

我预先已经在siteb.com的请求中设置了一个cookietest=1024.刷新一下可以看到这个时候的ajax请求已经把siteb的cookie带上了,但是又出现了另外一个错误

XMLHttpRequest cannot load http://siteb.com:9000/index.html. A wildcard ‘*’ cannot be used in the ‘Access-Control-Allow-Origin’ header when the credentials flag is true. Origin ‘http://localhost:8080‘ is therefore not allowed access. The credentials mode of an XMLHttpRequest is controlled by the withCredentials attribute.

意思就是当withCredentials=true时,Access-Control-Allow-Origin与请求的origin必须 严 格一致.再次修改nginx的配置:

1
2
3
4
5
6
7
8
location / {
root html;
index index.html index.htm;
add_header Set-Cookie "test=1024;";
add_header 'Access-Control-Allow-Origin' 'http://localhost:8080';
add_header 'Access-Control-Allow-Credentials' 'true';
}

再次刷新已经没有报错了.done!

再啰嗦一下

线上环境是没有这个问题的,因为都是同一个域发出的请求,那线下环境要跨域调试怎么办呢?一般来说有两种方式:

  • 将线上环境的nginx Access-Control-Allow-Origin开放成localhost+固定端口号专门用于测试
  • 可以在本地起一个nginx,把静态文件放进去,同时设置代理掉线上的API,模拟出同域的效果.