前端 ajax 跨域请求应该算是每个前端都应该有的 “理论基础”, 之所以说是 “理论基础” 是因为这是一道经典的面试题,所以基本每个前端都应该知道对应的解决方案。然而实际开发中其实很少会遇到跨域的情况,毕竟大多数的情况下都是访问同一个域内的东西。
先说说面试的答案吧,解决前端跨域请求主要有以下几种方案:
- JSONP 请求
- CORS 通过服务端配置
Access-Control-Allow-Origin
响应头实现跨域 - 后端设置代理 proxy 跨域
其他还有
document.domain
,window.name
等方案,由于实际使用场景不大,暂不做分析。
JSONP 的原理是利用 script 标签的加载不受同源策略的限制,动态的加载一个 <script>callback(data);</script>
标签并执行对应的 callback
来实现的。记住 JSONP 返回的其实是一个 callback
的函数调用,调用的参数(data
)由后端服务器根据传入参数(url.query
)和业务场景进行填充。
Access-Control-Allow-Origin
跨域方案更像是一个协商机制,对于跨域的请求,服务端在收到请求的时候判断请求是不是来源于被允许的域,如果是就设置对应的响应头告诉客户端该请求是被允许的,而客户端拿到响应头会去解析 Access-Control-Allow-Origin
判断这个跨域请求是不是被允许的,从而决定这个跨域请求的结果是否是合法的。(个人理解,如有不当之处还请指出)
proxy
代理的方案主要是将请求发送到同域的后端服务器上,再由同域的后端去像对应的目标服务器获取数据,前端代码只与同域的服务器进行数据交换,跨域发生在同域的服务器与目标服务器之间,对于前端请求而言不存在跨域。
本次项目中我们最终选择的是第二种方案——设置 Access-Control-Allow-Origin
,主要有以下两个原因:
- JSONP 能解决大多数的跨域场景,但无法实现
post
请求(实际只是加载一个script
标签),而且请求的参数也只能在url
中传递。而我们的业务场景中有不可避免的post
请求,所以无法满足需求。 - 在本来就有同域 node 端服务支持的情况下,使用
proxy
方案应该是最优方案,但由于资源不足,时间紧等原因最后放弃了该方案。
接下来说说遇到的一些问题:
问题 1:设置响应头为 Access-Control-Allow-Origin: *
无效
最开始后端同学在请求的响应头中设置了 Access-Control-Allow-Origin: *
,但是实际联调中 网络请求成功了,在 network
面板下也看到了对应的响应数据,但是程序里面的请求始终无法成功,控制台下提示 *
的值不被允许(当时只是叫后端同学改成返回对应的域,并没有去深究为什么 *
不行,后来看到是因为请求带上了 cookie
(做用户身份认证),这种情况下服务端响应头需要设置 Access-Control-Allow-Credentials : true
来运行客户端携带证书式访问,而此时 Access-Control-Allow-Control: *
是不被允许的)
后端通过请求 origin 设置 响应 对应 Access-Control-Allow-Origin
可参考这里: Access-Control-Allow-Origin 跨域设置多域名 Java 端逻辑上也是类似的。
问题 2:在 nginx
层和业务代码中都设置 Access-Control-Allow-Origin
跨域依旧存在
通过动态在业务代码中设置 Access-Control-Allow-Origin
对应允许跨域的响应头,已经能把请求跑通,完成跨域的操作了,但是如果遇到用户登录信息失效的情况就又有问题了,此时的流程为 request --> nginx --> filter --> response
用户登录信息失效的时候实际还未进入到对应接口的业务逻辑里面(响应里面未设置 Access-Control-Allow-Origin
头),虽然后端返回了 302
的信息但又跨域了,所以前端请求又错误了,而且并不能拿到具体的错误内容。所以后端的跨域响应头设置得提前。
于是我们在 nginx
层也添加了相同的头信息,此时的流程是 request --> nginx -(添加Access头)-> filter --> 业务代码 -(添加Access头)-> response
,在这里我们设置了两个相同的 Access-Control-Allow-Origin
头,
如何解决呢,既然我们设置一个 Access-Control-Allow-Origin
的时候可以跑通,现在我们去掉一个多余的头信息设置应该也能跑通,去掉哪一个呢?显然应该是业务代码里面那个,而应该在 nginx
上做,保证每个请求都正确的设置 Access-Control-Allow-Origin
头信息。
最后说说在后端接口支持跨域前如何先联调起来:
此时由于我们的已经能拿到后端返回的数据了(network
面板上能看到数据了),之所以现在我们代码中依然拿不到数据其实是因为浏览器的安全策略阻止了代码中对响应的访问,所以如果我们能临时的关闭浏览器的安全策略是否就能拿到对应的数据了呢,测试完过后答案是肯定的。
关闭浏览器安全策略方案可参考这里:chrome disable-web-security 关闭安全策略 解决跨域,mac 的情况下 --user-data-dir
参数需要指定一个文件目录。
该方法只限于联调阶段,实际场景中不可能让用户以禁止安全策略的方式运行浏览器访问我们的站点。
参考资料: