理性分析 AJAX 跨域问题

理性分析 JavaScript·发布于 7 年前
5 分钟阅读

跨域(Cross origin)问题是前端中比较常见的问题。 要解决跨域问题,我们需要明白是什么导致跨域问题的?


同源策略

跨域问题源于浏览器的安全策略。为了保护数据的安全性和完整性,浏览器使用了同源策略(Same origin policy),以防止恶意脚本从当前源获取其他源的敏感数据。

需要注意的是,浏览器并不限制当前源发送信息到其他源,而是限制接收其他源的数据。

同源策略是浏览器的安全策略。使用非浏览器程序就没有这个限制。例如,我们使用 postman 来测试 API 就没有跨域问题。使用 node.js 程序来访问接口也没有跨域问题。

同源策略规定了源必须相同。所谓的源,就是 url 文件路径之前的部分。例如:

https://www.baidu.com/index.html

在 index.html 之前的部分就称为源。源包括三个部分:协议,域名,端口。

在上述的 url 中, https 表示协议, www.baidu.com 表示域名,还有一个隐藏的端口号 443

http 和 https 都有对应的默认端口号,http 的默认端口为 80,https 对应的端口号为 443。如果想要访问其他的端口号,就需要在输入的时候显示输入了。

关于源,有一点要注意的是:源中的域名指的是狭义上的域名,三级域名和二级域名也算是不同域名。例如:www.baidu.combaidu.com算是不同的域名。

简而言之,https://www.baidu.com这个源只有 https://www.baidu.com:443是和它算是同源的,其它一个字符都不能变,对应的 ip 也算是不同源。

同源策略的限制

同源策略有两个要点:同源、限制脚本。

javascript 是脚本语言之一。受同源策略限制, javascript 无法获取其他源的 DOM ,Cookie、LocalStorage,IndexDB,并且 AJAX 请求无法正常获取数据。


跨域

有时候我们需要从一个源访问另外一个源的数据。比如前后端分离之后,前后端程序分别布置到两台服务器上,这个时候就出现了跨域问题。

跨域(Cross origin),从英文上来看,其实就是跨源。要解决跨域问题,本质上是绕过同源策略。

解决 AJAX 跨域问题,有两个解决方案:反向代理(从源服务器着手),CORS(从目标服务器着手)。

反向代理

反向代理可以在不改动前后端程序的情况下解决跨域问题。

先来了解下什么是代理(Proxy )。

日常生活中,我们有时会使用 VPN 来科学上网。这个时候会用到代理。我们所有的请求都将由这个代理服务器转发,然后返回给客户端。代理起到了请求转发的作用。

正向代理和反向代理

在使用 VPN 上网的时候,用户选择要访问的网站,将请求发送给代理服务器。代理服务器将请求转发到网站服务器,并将获取的结果返回给用户。这个过程中,请求目标是由用户决定的,代理服务器事先并不知道访问的目标服务器。这就是正向代理。

反向代理与正向代理相反,客户端向当前服务器发起请求,当前服务器将决定将请求转发到哪个目标服务器,这个目标服务器是由当前服务器配置的。这就是反向代理。

简而言之,正向代理和反向代理的区别就是正向代理的请求目标是由客户端决定的,反向代理的请求目标是由代理服务器决定的。

通过反向代理解决跨域问题

通过反向代理,我们就可以绕过同源策略了。因为我们的所有请求都是发送到反向代理服务器中,然后由反向代理服务器去请求目标服务器。反向代理服务器不受浏览器同源策略影响。这样解决了跨域问题。

nginx ,apache 等服务器都可以配置反向代理。

例如当前服务器为 http://localhost:80 想要把路径 / 下的请求转发到 http://localhost:8080 下。

在 nginx 中可以添加如下配置

location / {
  proxy_pass http://localhost:8080;
}

CORS

CORS 全称 Cross-origin resource sharing (跨域资源共享),要求 IE 版本不能低于 10。

既然浏览器同源策略是防止恶意脚本从当前源获取其它源的敏感数据,那么要是其它源想要主动对外开放部分数据,应该怎么办呢?

这时候 W3C 提供了一套标准,也就是 CORS。通过 CORS ,服务器可以选择性地开放部分数据。

最简单的配置就是在源服务器中配置响应头 Access-Control-Allow-Origin 选项,也就是访问控制允许的源。可以将其设置为 *,这样所有的源都可以获取该服务器的数据了。

当然了,CORS 标准中规定了一些安全设置。比如将请求分为简单请求和非简单请求。超出简单请求的范围的话,就需要额外的配置。

简单请求

简单请求的限定范围如下:

请求方法是以下 3 种方法之一:

1. HEAD
2. GET
3. POST

 HTTP的头信息不超出以下 5 种字段:
 
1. Accept
2. Accept-Language
3. Content-Language
4. Last-Event-ID
5. Content-Type:只限于 3 个值
application/x-www-form-urlencoded、multipart/form-data、text/plain

当发送简单请求时,浏览器会自动在请求头中加入 Origin 字段,它的值为当前源。目标服务器在接收到请求之后,如配置了 Access-Control-Allow-Origin 字段,就会添加 Access-Control-Allow-Origin 到响应头中。

当浏览器接收到响应时,会检测 Access-Control-Allow-Origin 的值。

  • 如不存在会抛出 No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8010' is therefore not allowed access.错误。
  • 如不匹配会抛出 The 'Access-Control-Allow-Origin' header has a value 'http://localhost:8010' that is not equal to the supplied origin.错误。
  • 匹配成功会则正常解析响应。

非简单请求

超过简单请求的范围的就是非简单请求了。非简单请求需要预检。在使用非简单请求的时候,浏览器会首先发送一次预检请求,以确认当前请求方法和请求头是否被允许,如被允许再发送一个请求来获取返回数据。

例如想要使用 delete 请求。这个时候,需要在目标服务器上配置 Access-Control-Request-Method ,也就是访问控制允许的请求方法,在里面添加 delete 请求。

同理,超出范围的请求头需要在目标服务器配置 Access-Control-Allow-Headers 。

CORS 默认是不发送 Cookie 信息的。这个时候,如果我们想要发送Cookie 的话,就必须同时在请求和目标服务器中配置。使用 xhr 发送 AJAX 请求的话,需要做如下配置:

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

并且在目标服务器中配置 Access-Control-Allow-Credentials 字段。

Access-Control-Allow-Credentials: true

出于安全的考虑,如要发送 Cookie 的话,Access-Control-Allow-Origin 就不能设为通配符。必须明确指定源。


总结

  1. 跨域问题源于浏览器的同源策略。
  2. 同源策略要求协议、域名、端口号必须相同,限制脚本访问其他源的数据。
  3. Ajax 跨域访问有两种解决方案:反向代理和 CORS 。其中反向代理是在源服务器中配置,CORS 是在目标服务器中配置。
    • 反向代理是通过代理服务器获取目标服务器的数据来绕过同源策略。
    • CORS 标准是通过开放目标服务器访问权限来提供数据。

相关知识点

  • JSONP
  • WebSocket

参考链接

  • 同源策略
    • 同源策略的限制
  • 跨域
    • 反向代理
      • 正向代理和反向代理
      • 通过反向代理解决跨域问题
    • CORS
      • 简单请求
      • 非简单请求
      • cookie
  • 总结
    • 相关知识点
    • 参考链接