web开发的跨域问题详解

本文由云+社区发表

作过 web 开发的同窗,应该都遇到过跨域的问题,当咱们从一个域名向另外一个域名发送 Ajax 请求的时候,打开浏览器控制台就会看到跨域错误,今天咱们就来聊聊跨域的问题。javascript

1. 浏览器的同源策略

同源的定义是:若是两个页面的*协议*端口(若是有指定)和*域名*都相同,则两个页面具备相同的。同源策略限制了从同一个源加载的文档或脚本如何与来自另外一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。前端

2. 跨域错误信息产生的缘由

为了说明问题,咱们能够作以下实验,咱们在本地搭建了开发环境, 由客户端 http://localhost:3001 向服务器 http://localhost:3000 发送两个请求,一个使用 javascript 异步请求数据,另外一个使用 img 标签请求数据,服务器收到请求后,打印接收到请求的日志,以下图所示:java

img客户端发送两个请求jquery

img服务端打印日志并处理请求web

代开客户端浏览器的控制台,能够看到发出了两个请求,而且都收到了状态码为 200 的响应,同时控制台报了一个错误,即 xhr 请求报错。由此咱们能够知道,之因此产生跨域错误信息,缘由有如下三条:ajax

  • 浏览器端的限制(服务端收到了请求并正确返回)
  • 发送的是 XMLHttpRequest 请求(使用 img 标签发送的请求为 json 类型,并不会报错)
  • 请求了不一样域的资源

只有同时知足了这三个条件,浏览器才会产生跨域错误。json

3. 解决跨域的思路

既然咱们知道了跨域错误产生的缘由,那么解决思路就很直观了,针对出错的三个缘由进行相应的处理便可,相应的解决思路也有三个方向:后端

  • 打破浏览器的限制
  • 不发送 XHR 请求
  • 解决跨域

下文将分别进行阐述。跨域

3.1 打破浏览器的限制

由上面分析结论可知,之因此出现跨域的错误,其实是客户端浏览器所作的限制,服务器并未进行限制,所以咱们能够经过设置浏览器,使其不进行跨域检查。实际上浏览器也提供了对应的设置选项。浏览器

以 MacOS 下的 Chrome 浏览器为例,在终端中使用命令

open -n /Applications/Google\ Chrome.app/ --args --disable-web-security  --user-data-dir=/Users/your-computer-account/MyChromeDevUserData/

打开浏览器,便可禁用 Chrome 浏览器的安全检查功能,同时也会禁用跨域安全检查功能,这样再次拿前面的例子进行测试,发现此时不会报错,同时也能够正确拿到服务端返回的数据。

img禁用浏览器安全检查功能

这种方式虽然能够实现跨域,可是须要每一个用户都对浏览器进行设置,同时可能致使潜在的安全隐患,正常状况下不实用。但这个例子充分说明了,跨域错误是前端浏览器所作的限制,与后台服务无关。

3.2 JSONP实现跨域

根据思路2,既然跨域问题产生的缘由是由于客户端发送了 Ajax 请求,那么咱们打破这个条件便可。具体实现方式就是使用 JSONP 来进行跨域请求。

JSONP,是 JSON with Padding 的简称,它是 json 的一种补充使用方式,利用 script 标签来解决跨域问题。JSONP 是非官方协议,他只是先后端一个约定,若是请求参数带有约定的参数,则后台返回 javascript 代码而非 json 数据,返回代码是函数调用形式,函数名即约定值,函数参数即要返回的数据。

JSONP 的实现原理以下图所示:

imgJSONP实现原理

首先在客户端 js 中定义一个函数(假设名为 handler),而后动态建立一个 script 标签插入页面中,script 标签的 src 属性即要调用的地址,同时,在调用的 url 中加入一个服务端约定的参数(假设名为 callback,参数值为已定义的函数名 handler),服务端收到请求,若是发现请求的 url 中带有约定的参数,那么就返回一段函数调用形式的 javascript 代码,该段代码的函数名即为 callback 参数的值 handler,函数的参数即为待返回的数据。这样,客户端拿到返回结果后就会执行 handler 函数,对返回的数据进行处理。

咱们使用 jquery 向服务端发送一个 JSONP 格式的请求,从浏览器控制台能够看到请求和对应的响应,以下图所示:

imgJSONP请求

imgJSONP请求的响应

由上图能够看到,发送JSONP请求时,请求的 Type 为 script 类型而非 xhr 类型,这样就打破了跨域报错的三个必要条件,不会产生跨域错误,同时也验证了服务端返回的数据格式为 javascript 代码调用的形式,其中 Jquery331045** 这一长串函数名是 jquery 自动生成的。

因为 JSONP 的原理是使用 script 标签来加载数据,因此它的兼容性很好,可是使用 JSONP 来解决跨域问题存在如下缺陷:

  1. 只能发送 GET 请求
  2. 发送的不是 XHR 请求,这样致使 XHR 请求中的不少事件都没法进行处理
  3. 服务端须要改动

3.3 跨域资源共享CORS

CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它容许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。CORS 基于 http 协议关于跨域方面的规定,使用时,客户端浏览器直接异步请求被调用端服务端,在响应头增长响应的字段,告诉浏览器后台容许跨域。

img跨域错误

回到文章开始的这个跨域错误信息,能够看到错误的具体信息是:服务端没有设置Access-Control-Allow-Origin 这个响应头从而致使报错,经过设置 Access-Control-Allow-Origin: * 这个响应头,咱们能够解决问题。可是,这种设置能知足全部状况吗? 更进一步,使用 CORS 时浏览器如何检查跨域错误? 前面咱们有讲到,虽然浏览器报错,可是在这以前服务端已经接受了请求,那么,浏览器老是先发出请求后再进行判断吗?下面咱们一一讨论。

3.3.1 浏览器如何检查跨域错误

浏览器检查跨域错误的基本原理是:

浏览器检测到 ajax 请求的域与当前域不一致,会在请求头中增长 Origin 字段,而后检查服务端响应头 Access-Control-Allow-Origin,若是不存在或不匹配,则报跨域错误。

img浏览器检查跨域错误原理

3.3.2 浏览器老是先发出请求,而后根据是否有 Access-Control-Allow-Origin 响应头来判断吗

答案是,对于简单请求,是;而对于非简单请求,不是。非简单请求的状况下,浏览器并非直接请求所需资源,而是会先发出一个预检请求,预检请求经过后才会对所需资源进行请求。

img非简单请求预检请求

这里涉及到的简单请求和非简单请求的概念,那么简单请求和非简单请求有什么区别呢?MDN 对非简单请求进行了定义,知足下列条件之一,即为非简单请求:

  1. 使用了下列 HTTP 方法:PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH
  2. 使用了除如下首部以外的其余首部:Accept、Accept-Language、Content-Language、Content-Type
  3. Content-Type首部的值不属于下列其中一个: application/x-www-form-urlencoded、 multipart/form-data、 text/plain
  4. 请求中的 XMLHttpRequestUpload 对象注册了任意多个事件监听器
  5. 请求中使用了ReadableStream对象

简单来讲,除了咱们平时使用最多的 GET 和 POST 方法,以及最常使用的 Accept、Accept-Language、Content-Language 和 类型为 application/x-www-form-urlencoded、 multipart/form-data、 text/plain 的 Content-Type 请求头,其余基本都是非简单请求。对于这些非简单请求,浏览器会发出两个请求,第一个为 OPTIONS 碰见请求,碰见请求的响应检查经过后才会发出对资源的请求。

img非简单请求过程

生产环境下,若是须要发送非简单跨域请求,每次两个请求会增长响应时间,为此,W3C 标准中增长了另外一个响应头 Access-Control-Max-Age 参数,该响应头代表了对于非简单请求的预检请求浏览器的缓存时间,在缓存有效期内,非简单请求能够不发送预检请求,另外,实际开发中,能够在服务端设置接收到的请求方法是 OPTIONS 时,直接返回 200,这样也能加快响应。

3.3.3 设置 Access-Control-Allow-Origin: * 就行吗

img带cookie的跨域

当咱们须要发送带 cookie 的请求时,Access-Control-Allow-Origin 直接设置为通配符 * 时是没法经过浏览器的检查的,此时该响应头的值必须与发出请求的域彻底匹配才行,另外,还须要设置 Access-Control-Allow-Credentials 响应头的值为 true,表示支持带 cookie 的跨域请求。

3.3.4 CORS请求头和响应头总结

请求头:

  • Origin: 浏览器发出 Ajax 跨域请求以前会添加此头部,值为发送请求的域
  • Access-Control-Request-Method:使用了除 GET、POST 请求方法以外的方法,浏览器会添加此头部,值为当前请求方法
  • Access-Control-Request-Headers:使用了自定义头部或除了Accept、Accept-Language、Content-Language、Content-Type 以外的头部,浏览器会添加此头部,值为当前的请求方法

响应头:

  • Access-Control-Allow-Origin: 表示服务端容许哪些域请求资源
  • Access-Control-Allow-Methods: 当客户端包含 Access-Control-Request-Method 请求头时,服务端须要响应该头部,值一般由 Reauest 的 header 中 Access-Control-Request-Method 取得
  • Access-Control-Allow-Headers: 当客户端包含 Access-Control-Request-Headers 请求头时,服务端须要响应该头部,值一般由 Reauest 的 header 中 Access-Control-Request-Headers 取得
  • Access-Control-Expose-Headers: 指出客户端经过 XHR 对象的 getResponseHeaders 方法能够获取的响应头有哪些
  • Access-Control-Allow-Credentials: 容许带 cookie 的跨域请求
  • Access-Control-Max-Age: 预检请求的缓存时间

4. 总结

本文介绍了跨域的缘由,重点介绍了使用 JSONP 和 CORS 解决跨域问题的方法。除此以外,实际开发中还其余各类解决跨域问题的思路,本质上,这些方法都是打破跨域错误的三个条件,你们能够自行查资料了解一下。

此文已由做者受权腾讯云+社区在各渠道发布

相关文章
相关标签/搜索