Skip to content

浏览器之跨域 #17

Open
Open
@myLightLin

Description

@myLightLin

平时工作中,在与后端同事对接接口时,经常会遇到跨域问题。当请求产生跨域的时候,实际上是被浏览器拦截了,并没有到达服务端,所以遇到跨域别急着找后端大哥说接口出问题了~ 实际上请求还没到服务端呢。那为啥网络请求会跨域呢?这就要从浏览器的同源策略说起。

同源策略

一个 url 大概长这样子: https://www.baidu.com:443,主要由 协议域名 还有 端口号 组成,其中如果是默认端口号,那可以省略不写, http 的默认端口号是 80 ,https 的默认端口号是 443。当两个 url 以上三个信息全部相同时,我们就称这两个 url 是 同源 的。

浏览器的 同源策略 规定只有同一个源下的文档及脚本才有权限读取访问一些信息。比如:

  • cookie 、localStorage 和 IndexDB ,每个源域名下的存储信息是独立的
  • DOM 的获取
  • AJAX 请求的发送

那浏览器为什么要实行这个策略?为了 安全。如果不限制同源请求访问,那相当于一个源下的信息可以被任意的获取,这在信息安全方面毫无疑问是极大的隐患,容易被黑客利用攻击。

跨域的表现

当我们在前端发起一个 Ajax 请求时,如果源请求和目标请求 协议域名 还有 端口号 有任意一个不同,那么这个请求就违反了浏览器的同源策略,于是就产生了跨域。

请求又分为简单请求和复杂请求。简单请求 可以直接发送,它必须符合以下条件:

  • 请求方法是 GET, POST, HEAD 其中之一
  • 请求头只包含 AcceptAccept-LanguageContent-LanguageContent-Type
  • Content-Type 的值为 text/plainmultipart/form-dataapplication/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.comb.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,就能获取到数据?
我们可以看看这个过程发生了什么:

  1. script 的 src 属性发出了一个 http 请求
  2. 服务器接收到这个请求,那么这时,它需要事先知道客户端指定的回调函数名字,然后获取它
  3. 获取这个函数后做什么呢?服务器需要动态生成一段 JS 代码,在这段代码中,把数据当作参数传递给这个函数
  4. 浏览器拿到服务器的响应后,执行这段代码,于是我们前面指定的 callback 得以顺利执行,数据也获取到了

这个过程其实是存在安全问题的,因为是直接执行服务器返回的代码,如果服务端被攻击了,可能会执行到恶意代码

跨域时 cookie 的携带问题

  • 必须在初始化 Ajax 请求时,设置 withCredentialstrue
  • 检查下 samesite 属性值,确保不会限制 cookie,值为 none 则可以携带
  • 服务端要设置首部字段 Access-Control-Allow-Credentialstrue

总结

  • 为了保证信息安全,浏览器规定了同源策略
  • 请求违反了同源策略(协议、域名、端口有一个不同),就是跨域
  • 跨域的解决方案有 document.domain、JSONP 、postMessage 和 nginx 反向代理
  • 跨域时注意 cookie 的携带问题

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions