最近在做前后端交互的时候遇到了特殊的跨域问题。其实自己平时做项目的时候也多多少少处理过跨域问题,但是这次情况特殊。正好借此机会将跨域问题的解决方法做一下总结,以备不时之需。
情景
自己前段时间在学习前端,所以想趁着这个机会自己搞个小工具玩玩。所以就有了如下这个极其简陋的弹幕网站。
为了实现弹幕的持久保存,自己尝试前后端交互将弹幕存储在数据库中,然后前端每间隔一段时间就从后台拉取一次数据。
实现其实比较简单,但是当我在本地开始测试向后台发送请求的时候,问题出现了:
很明显这是一个跨域问题,所以接下来我们就要动用我们所学的知识去解决它。
首先第一步,分析问题原因:什么是跨域?
什么是跨域?
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源
跨域其实不是什么bug,而是由于浏览器为了安全起见指定了一系列“同源策略”,这些同源策略可以保证用户信息的安全,防止被恶意的网站窃取数据。
该政策限制网页的某些行为必须限制在与自己“同源”的网页中才能进行,如果不是同源,就会导致该行为无法生效,也就是跨域失败。
这里出现了两个概念,一个是同源,一个是跨域行为
同源的定义包含三个方面
- 协议相同
- 域名相同
- 端口相同
只有三个条件都满足,才能认定两个网页是”同源“
跨域行为(自己定义的名词,大概就是我们会被同源政策影响到的操作)随着互联网的发展,范围变得越来越宽泛,一般我们常见的跨域行为包括
- 获取 Cookie、LocalStorage 和 IndexDB
- 获得DOM和JS对象
- AJAX请求
我们可以看到在前后端交互中最常见的AJAX请求也赫然在列,在前后端交互中解决跨域问题的不可避免的。
那为什么要定义同源策略呢?没有跨域限制不是更好吗?
如果没有跨域限制,网页将很容易受到XSS、CSRF等攻击,因为没有限制,恶意网站同样可以自由地发起攻击,这将大大提高网站的维护成本。因此,同源策略其实是一把双刃剑,只是在保护网页的同时,偶尔总会误伤友军。
知道了问题的根源,我们就可以对症下药,寻找解决方案
解决方案
我们可以看到,跨域问题的关键在于我们的M请求处于限制范围内,没有做到同源>,从而导致的。
重点已经标出来了,其实我们解决的方法也就是从这两个思路着手
- 采用不在同源策略的行为操作
- 想办法让行为处于同源状态
这里我们指针对AJAX请求,对于其他诸如cookie、iframe等的跨域方案,其实参考相关的博客相信一定能得到答案
1.JSONP跨域
我们可以通过在AJAX请求中定义JSONP类型实现跨域,虽说如此,但JSONP本质上采用的是和AJAX完全不同的请求方式。
传统的AJAX请求其实是xhr
的异步请求,而JSONP本质上是去构建一个<script>
标签,利用script
标签中的src
不受同源政策的限制,在src
中填写后端URL
并添加回调函数,获取到的数据就通过回调函数处理。
参考阮一峰的博客,实现思想大致如此:
1 | function addScriptTag(src) { |
由于
<script>
元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo
函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse
的步骤。
如何在AJAX实现:
1 | $.ajax({ |
如何在vue上实现:
1 | this.$http.jsonp('http://www.domain2.com:8080/login', { |
从实现原理上可以看出JSONP还是存在弊端,那就是使用JSONP必须是GET请求,如果要POST请求实现跨域,还是需要使用其他方法
2.WebSocket
websocket
本身就是一种通信协议,通过websocket
通信,实际上就可以跨过同源策略, 实现某种意义上的”同源”。
下面是websocket
请求的HTTP头信息,重点关注Origin
字段,这是实现跨域的关键
1 | GET /chat HTTP/1.1 |
Origin字段表示该请求的请求源,只要Origin字段中的源域名和请求的目的域名是同一个,就可以通过同源策略中的域名一致,实现跨域。
如果允许通信,WebSocket的响应头如下
1 | HTTP/1.1 101 Switching Protocols |
3.CORS
CORS即跨域资源共享”(Cross-origin resource sharing),它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
这是解决跨域问题的常用方法。
实现原理
其实现原理如图
CORS请求主要分成两类:简单请求和非简单请求。
满足以下条件的就是简单请求,否则就是非简单请求
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
:只限于三个值application/x-www-form-urlencoded
、multipart/form-data
、text/plain
简单CORS请求只是在请求的时候在http头中加入Origin字段
非简单CORS请求的话,浏览器会在正式通信后先发送预检请求,先询问服务器是否允许请求,得到响应,检查相关字段后就可以做出回应,发起正式请求
假设现在发起一段js脚本
1 | var url = 'http://api.alice.com/cors'; |
其中预检请求的请求方法是OPTIONS,具体请求头类似如下
1 | OPTIONS /cors HTTP/1.1 |
主要关注三个字段
Origin
表示请求来自哪个源
Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是
PUT
。Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是
X-Custom-Header
。
得到响应如下之后就能确认允许跨域请求
1 | HTTP/1.1 200 OK |
其中
1 | //表示支持任意跨域请求 |
实现方式
这里主要是后端的操作,这里用了java
springboot
的跨域方式做为样例
1 |
|
其实就是在服务端响应的时候添加请求头,springboot
还支持在不同controller
上使用注解添加
4.nginx代理跨域
这个原理也简单,其实就是让前端和后端处于同源上,利用nginx的反向代理可以修改请求的域名、端口,也能添加cookie信息啥的实现跨域
1 | #proxy服务器 |
另外使用一些中间件的代理方式其原理都是这回事,这里就不加赘述
分析问题
一通分析
上面解决方案说了一大堆,但最终还是要回归我们的问题,这次,我们开始对症下药。首先再看一遍报错日志:
嗯?好像和想象中的不太一样,常见的跨域问题应该如同:
这种,看起来其中有诈?
果不其然,通过后台添加跨域设置,我们的报错信息依然没有变化。
这时我们就需要对报错信息好好分析(其实这应该是分析日志的第一步,为了强行引入跨域解决方案,因此特地将分析放在了后面)
这句话引起了我的注意
Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
通过查阅资料发现,原来这里我的前后端交互都是再本地实现,本地打开html使用的file协议,但是file协议的请求无法被浏览器认可,网上提供的方法如下
在谷歌浏览器下的快捷方式位置

在目标处添加:
1 | "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" -args --disable-web-security --user-data-dir --allow-file-access-from-files |
大概就是这样,但是我还是不推荐使用这种方法,因为这样的方式并不是特别优雅的解决方法
另外的尝试
另外的解决方法就是在本地部署nginx,诸如上面提到过的解决方法,不通过file协议打开文件。
什么是file协议的打开方式?
大概就是这种
要换成用http形式打开的方式,诸如这种
还有如果不嫌麻烦直接把网页部署到服务器上也是一种解决方法
意外的结果
但是!!最终问题还是没有得到解决!这可把我难到了。。
事必有因,经过一个多小时的不懈努力,我终于找到了问题的根源
————
ajax的请求URL必须以http的格式。。。。
啊啊啊啊果然还是我太菜了。。
因为刚学ajax,对其原理不熟,导致最后出现了这种问题。