Description
平时工作中,在与后端同事对接接口时,经常会遇到跨域问题。当请求产生跨域的时候,实际上是被浏览器拦截了,并没有到达服务端,所以遇到跨域别急着找后端大哥说接口出问题了~ 实际上请求还没到服务端呢。那为啥网络请求会跨域呢?这就要从浏览器的同源策略说起。
同源策略
一个 url 大概长这样子: https://www.baidu.com:443
,主要由 协议
,域名
还有 端口号
组成,其中如果是默认端口号,那可以省略不写, http 的默认端口号是 80
,https 的默认端口号是 443
。当两个 url 以上三个信息全部相同时,我们就称这两个 url 是 同源
的。
浏览器的 同源策略
规定只有同一个源下的文档及脚本才有权限读取访问一些信息。比如:
- cookie 、localStorage 和 IndexDB ,每个源域名下的存储信息是独立的
- DOM 的获取
- AJAX 请求的发送
那浏览器为什么要实行这个策略?为了 安全
。如果不限制同源请求访问,那相当于一个源下的信息可以被任意的获取,这在信息安全方面毫无疑问是极大的隐患,容易被黑客利用攻击。
跨域的表现
当我们在前端发起一个 Ajax 请求时,如果源请求和目标请求 协议
,域名
还有 端口号
有任意一个不同,那么这个请求就违反了浏览器的同源策略,于是就产生了跨域。
请求又分为简单请求和复杂请求。简单请求
可以直接发送,它必须符合以下条件:
- 请求方法是
GET
,POST
,HEAD
其中之一 - 请求头只包含
Accept
,Accept-Language
,Content-Language
,Content-Type
Content-Type
的值为text/plain
,multipart/form-data
,application/x-www-form-urlencoded
其中之一
不符合以上条件的请求就是复杂请求
,复杂请求在发送之前,浏览器需要先发一个预检请求,请求方法为 OPTIONS
,询问服务器是否支持跨域,内容大概是这样:
OPTIONS /resource/foo
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Content-Type, x-requested-with
Origin: https://foo.bar.org
服务器会在响应 header 的 Access-Control-Allow-*
系列字段里包含它支持的源信息,大概是下面这样:
HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: https://foo.bar.org
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Allow-Headers: Content-Type, x-requested-with
Access-Control-Max-Age: 86400
如果预检请求失败,则跨域报错信息如下:
Access to XMLHttpRequest at '×××' from origin '×××' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
遇到这样的错误提示,建议找后端同学,先检查下服务端有没配置好 Access-Control-Allow-Origin
这个字段,特别注意环境区分,有时特定环境下忘记配了也是常有的事。通常你的本机 ip : http://localhost:8080
要添加进允许源里去。
跨域的解决方案
document.domain = '×××'
适用于站点和服务器享有相同的基本域名,比如a.example.com
和b.example.com
这时可以设置 document.domain = 'example.com' 来规避跨域JSONP
利用script
标签没有跨域限制的特性,通过 src 属性指向要访问的地址并提供一个回调函数来接收数据CORS
跨域资源共享,上面已经介绍过了,通过OPTIONS
预检请求与服务器协商,比 JSONP 安全postMessage
window.postMessage 这个 API 可以跟另一个页面通信,传递信息,而不会有跨域问题- 使用
nginx 反向代理
通常是服务端同学去操作
JSONP 的简单实现
<script type="text/javascript">
var handleData = function (data) {
console.log(data)
}
var url = 'https://example.com?callback=handleData'
var script = document.createElement('script')
script.setAttribute('src', url)
document.getElementsByTagName('head')[0].appendChild(script)
</script>
JSONP 的原理
思考一个问题:为什么我们在 script 的 url 后面提供了一个 callback,就能获取到数据?
我们可以看看这个过程发生了什么:
- script 的 src 属性发出了一个 http 请求
- 服务器接收到这个请求,那么这时,它需要事先知道客户端指定的回调函数名字,然后获取它
- 获取这个函数后做什么呢?服务器需要动态生成一段 JS 代码,在这段代码中,把数据当作参数传递给这个函数
- 浏览器拿到服务器的响应后,执行这段代码,于是我们前面指定的 callback 得以顺利执行,数据也获取到了
这个过程其实是存在安全问题的,因为是直接执行服务器返回的代码,如果服务端被攻击了,可能会执行到恶意代码
跨域时 cookie 的携带问题
- 必须在初始化 Ajax 请求时,设置
withCredentials
为true
- 检查下 samesite 属性值,确保不会限制 cookie,值为
none
则可以携带 - 服务端要设置首部字段
Access-Control-Allow-Credentials
为true
总结
- 为了保证信息安全,浏览器规定了同源策略
- 请求违反了同源策略(协议、域名、端口有一个不同),就是跨域
- 跨域的解决方案有 document.domain、JSONP 、postMessage 和 nginx 反向代理
- 跨域时注意 cookie 的携带问题