fi3ework's Studio.

AJAX跨域请求分析

2017/05/27 Share

AJAX跨域请求分析

引出

首先看一段原生调用AJAX的代码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//原生调用AJAX
let xhr = new XMLHttpRequest();
let url1 = "https://free-api.heweather.com/v5/now?city=beijing&key=dce751438a22402b9c8c1fcb95e3ce82"
let url2 = "http://api.k780.com/?app=weather.future&weaid=1&appkey=25688&sign=f1b67fe3f11c62d94d613a42f7fbd7b9&format=json"
xhr.open("GET", url1, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
console.log("JSON by native:");
console.log(xhr.responseText);
}
}
}
xhr.send();

url1url2两个天气的接口,url1来自和风天气url2来自NOWapi,对url1的调用结果为

json1

对url2调用的结果为

json2

造成这两个结果不同的原因就是AJAX在调用时的同源策略,下面将进行分析。

跨域

同源的反面——就是非同源,也就是跨域,首先确定跨域的定义(引自浏览器的同源策略):

如果协议,端口(如果指定了一个)和域名对于两个页面是相同的,则两个页面具有相同的

具体的大家可以看这篇文章跨源资源共享 Cross Origin Resource Sharing(CORS),讲的非常好了。

原理

从MDN的定义上来说:

出于安全考虑,浏览器会限制脚本中发起的跨域请求。比如,使用 XMLHttpRequestFetch 发起的 HTTP 请求必须遵循同源策略。因此,Web 应用通过 XMLHttpRequest 对象或 Fetch 仅能向同域资源发起 HTTP 请求。

但禁止跨域访问显然限制了网站的能力,所以浏览器实现了合理的跨域请求,JS高程三:

CORS(Cross-Origin Resource Sharing,跨源资源共享)是 W3C 的一个工作草案,定义了在必须访
问跨源资源时,浏览器与服务器应该如何沟通。 CORS 背后的基本思想,就是使用自定义的 HTTP 头部
让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。

这个自定义的头部就是Origin字段,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。

Firefox 3.5+、 Safari 4+、 Chrome、 iOS 版 Safari 和 Android 平台中的 WebKit 都通过 XMLHttpRequest
对象实现了对 CORS 的原生支持。在尝试打开不同来源的资源时,无需额外编写代码就可以触发这个行
为。 要请求位于另一个域中的资源,使用标准的 XHR 对象并在 open()方法中传入绝对 URL 即可。

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

当一个A网址的网页向B网址发送请求的时候,B的server就会根据A发出的请求的头部信息决定是否允许访问,对A的response header中会包含Access-Control-Allow-Origin来告诉浏览器B网址的数据是否能被A对应的域访问到(如果没有这个头部,或者有这个头部但源信息不匹配,浏览器就会驳回请求)。

默认情况下,B网址不允许跨域访问,不过B端server通过设置Access-Control-Allow-Origin可以允许特定的域访问。

B端的server可以设置

1
Access-Control-Allow-Origin: http://siteA.com

来允许A域名下的访问请求(当然也可以设为*,就是公共资源了,谁都可以访问)。

现代浏览器并不会完全阻止跨域请求,如果A网页向B网页发送跨域请求,浏览器还是会在网络层取得请求页面然后检查response header中有没有将A域设为允许访问的域。(有些文章中写道浏览器禁止跨域发送请求的说法其实是不正确的,无论是否跨域,浏览器都会向服务器发送请求IE7及以下会阻止,然后根据Response Headers来确定数据是否成功访问)。如果B端的server没有允许A域进行访问,那么浏览器将会触发XMLHttpRequest‘s error事件并阻止JS代码访问返回到数据(就像上面提到的,返回的response header中没有Access-Control-Allow-Origin字段)。

简单请求

某些请求不会触发 CORS 预检请求。本文称这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。若请求满足所有下述条件,则该请求可视为“简单请求”:

过程

  1. 首先客户端通过XHR向服务器发送HTTP请求,其中客户端和服务器之间使用 CORS 首部字段来处理跨域权限(如果服务器通过则允许跨域访问),即Origin字段,HTTP请求头如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    GET /v5/now?city=beijing&key=dce751438a22402b9c8c1fcb95e3ce82 HTTP/1.1
    Host: free-api.heweather.com
    Connection: keep-alive
    Origin: null
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
    Accept: */*
    Accept-Encoding: gzip, deflate, sdch, br
    Accept-Language: zh-CN,zh;q=0.8
  2. 然后是服务器进行返回,分为两种情况:

    1. 服务端允许

      1
      2
      3
      4
      5
      6
      7
      8
      Access-Control-Allow-Origin:*
      Connection:keep-alive
      Content-Length:362
      Content-Type:application/json;charset=UTF-8
      Date:Thu, 25 May 2017 15:55:41 GMT
      Server:nginx/1.9.10
      Set-Cookie:JSESSIONID=6A2D4564076C2A7AADA8F0FAF8B3BFC1; Path=/; HttpOnly
      Set-Cookie:bug_token=74985bfc9d79459c8916df1349e608d5; Path=/; HttpOnly

      服务端返回的 Access-Control-Allow-Origin: * 表明,该资源可以被任意外域访问,

    2. 服务端拒绝

      1
      2
      3
      4
      5
      Connection:keep-alive
      Content-Length:3317
      Content-Type:application/json; charset=utf-8;
      Date:Thu, 25 May 2017 14:58:12 GMT
      Server:nginx/1.10.3

      相比服务端允许的情况少了,少了如下字段(可以证明即使被拒绝,也向服务器发送了请求,只不过被服务器发了好人卡)

      1
      2
      3
      Access-Control-Allow-Origin:*
      Set-Cookie:JSESSIONID=6A2D4564076C2A7AADA8F0FAF8B3BFC1; Path=/; HttpOnly
      Set-Cookie:bug_token=74985bfc9d79459c8916df1349e608d5; Path=/; HttpOnly

      其中Access-Control-Allow-Origin参数值指定了允许访问该资源的外域 URL。url2请求失败时的错误信息No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.就是在说request response中没有Access-Control-Allow-Origin这个字段,导致报错。而Access-Control-Allow-Origin的值为*或者是发送请求的域时,浏览器才允许我们拿到它页面的数据进行下一步处理。

      可能返回的请求

      有三个与CORS请求相关的字段,都以Access-Control-开头。

      (1)Access-Control-Allow-Origin(必须)

      该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

      (2)Access-Control-Allow-Credentials(可选)

      该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

      (3)Access-Control-Expose-Headers(可选)

      该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

TIPS

  • Q:stateReady和status有什么区别

    A:stateReady用来指示XHR请求的状态(比如请求还没有发送(0), 请求完成并且已收到response(4)),stateReady是由客户端进行判断的。而status用来指示对服务器的请求是否成功,由服务器决定返回值。所以在进行onreadystatechange时,需要先进行判断readystate确定本地的请求已完成,再通过status去判断服务器对请求的响应。

  • Q:为什么使用Postman可以直接跨域请求,而且浏览器必须通过JSONP才能进行跨域?

    A:Quoted from Cross-Origin XMLHttpRequest:

    Regular web pages can use the XMLHttpRequest object to send and receive data from remote servers, but they’re limited by the same origin policy. Extensions aren’t so limited. An extension can talk to remote servers outside of its origin, as long as it first requests cross-origin permissions.

普通的网页可以用使用XHR和远程服务器发送和接收数据,可是他们需要遵循同源策略。但是拓展工具不受限制,他们只要先申请了跨域许可,就可以不受同源策略的限制与服务器进行交互。

CATALOG
  1. 1. AJAX跨域请求分析
    1. 1.1. 引出
    2. 1.2. 跨域
    3. 1.3. 原理
    4. 1.4. 简单请求
      1. 1.4.1. 过程
        1. 1.4.1.0.1. 可能返回的请求
  2. 1.5. TIPS