跨域的解决方法(一)

719 阅读5分钟

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战

附件什么是同源策略的链接:juejin.cn/post/699517…

CORS(跨域资源共享)

CORS(Cross-origin resource sharing,跨域资源共享)是一个 W3C 标准,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。其核心思想是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,来决定请求或响应是应该成功,还是失败。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信

CORS请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。 只要同时满足以下两大条件,就属于简单请求

  1. 请求方法是以下三种方法之一:
  • HEAD
  • GET
  • POST
  1. HTTP的头信息不超出以下几种字段:
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

application/x-www-form-urlencoded

最常见 POST 提交数据的方式。浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据, 数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

multipart/form-data

需要在表单中进行文件上传时,就需要使用该格式

text/plain

纯文本格式

凡是不同时满足上面两个条件,就属于非简单请求

浏览器对这两种请求的处理,是不一样的。

简单请求

  1. 在请求中需要附加一个额外的 Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。例如:Origin: http://www.laixiangran.cn
  2. 如果服务器认为这个请求可以接受,就在 Access-Control-Allow-Origin (这个字段是必须的) 头部中回发相同的源信息(如果是公共资源,可以回发 * )。例如:Access-Control-Allow-Origin:http://www.laixiangran.cn
  3. 没有这个头部或者有这个头部但源信息不匹配,浏览器就会驳回请求。正常情况下,浏览器会处理请求。注意,请求和响应都不包含 cookie 信息。
  4. 如果需要包含 cookie 信息,ajax 请求需要设置 xhr 的属性 withCredentials 为 true,服务器需要设置响应头部 Access-Control-Allow-Credentials: true
  5. Access-Control-Allow-Credentials,它是一个可选值,它的值是一个布尔值,表示是否允许发送Cookie。 默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。

非简单请求

浏览器在发送真正的请求之前,会先发送一个 预检(Preflight) 请求给服务器,这种请求使用 OPTIONS 方法,发送下列头部:

  • Origin:与简单的请求相同。
  • Access-Control-Request-Method: 请求自身使用的方法。
  • Access-Control-Request-Headers: (可选)自定义的头部信息,多个头部以逗号分隔。

例如:

Origin: http://www.laixiangran.cn
Access-Control-Request-Method: POST
Access-Control-Request-Headers: NCZ

发送这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部与浏览器进行沟通:

  • Access-Control-Allow-Origin:与简单的请求相同。
  • Access-Control-Allow-Methods: 允许的方法,多个方法以逗号分隔。
  • Access-Control-Allow-Headers: 允许的头部,多个方法以逗号分隔。
  • Access-Control-Max-Age: 应该将这个 Preflight 请求缓存多长时间(以秒表示)。

优点

  • CORS 通信与同源的 AJAX 通信没有差别,代码完全一样,容易维护。
  • 支持所有类型的 HTTP 请求。

缺点

  • 存在兼容性问题,特别是 IE10 以下的浏览器。
  • 第一次发送非简单请求时会多一次请求。

JSONP 跨域

JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。

它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function () {
  addScriptTag('http://example.com/ip?callback=foo');
}

function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};

上面代码通过动态添加<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。

服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。

优点

  • 使用简便,没有兼容性问题,目前最流行的一种跨域方法。

缺点

  • 只支持 GET 请求。
  • 由于是从其它域中加载代码执行,因此如果其他域不安全,很可能会在响应中夹带一些恶意代码。
  • 要确定 JSONP 请求是否失败并不容易。虽然 HTML5 给 script 标签新增了一个 onerror 事件处理程序,但是存在兼容性问题。

document.domain 跨域

对于主域名相同,而子域名不同的情况,可以使用 document.domain 来跨域,可以规避同源政策,拿到DOM。这种方式非常适用于 iframe 跨域的情况。

对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。

  • 片段识别符(fragment identifier)
  • window.name
  • 跨文档通信API(Cross-document messaging)