跨域CORS浏览器端&&服务器端详解

1,318 阅读10分钟

什么是跨域

跨域是由于浏览器的同源策略导致,同源策略限制运行在浏览器的脚本在未获得明确授权情况下,无法访问不同源的资源,所谓的不同源指的是【协议,域名,端口】三者任意一者不同则为不同源。只有三者完全相同才是同源的。(这里需要注意的是,同源策略是浏览器所规范的限制,比如浏览器中的http://aaa.com:8090的脚本希望访问http://bbb.com的服务器资源会受到同源策略限制。但是如果使用比如postman这种获取数据工具则不会有同源策略的限制。)

跨域解决方案之CORS(cross-origin-resourse-sharing:跨域资源共享)

2014年1月16号CORS作为http协议的扩充部分正式发布,主要定义了客户端和服务端的沟通机制,也就是所谓的协议。它允许浏览器向跨源服务器发送XMLHttpRequest请求。但是仅依靠浏览器发送请求并不能实现资源访问,还需要服务器端的配合操作,才能发挥cors作用。所以cors其实是浏览器与服务器共同努力的结果。

CORS中 浏览器与服务器分别做了什么:

  • 1,浏览器在发现有请求发生跨域状况时,(位于http://aaa.com:8090的ajax请求要访问http://bbb.com的资源), 会自动将当前请求分为简单请求非简单请求发送,这两种请求发送的请求是不一样的。(注意:这个动作基本都是由浏览器去完成)
  • 2,服务器会对当前浏览器发出的简单请求以及非简单请求进行处理。响应浏览器请求,完成跨域资源共享。

浏览器:浏览器判断当前请求是简单请求还是非简单请求

当浏览器发出跨域请求时,会自动将请求分为简单请求,非简单请求,当请求同时满足下面两个条件,就是简单请求,否则是非简单请求

  • 当请求方式是head、post、head中任意一个
  • 请求的header是
Accept:'', // 接受返回数据类型 
Accept-Language:'', // 
Content-Language:'', //
Content-Type: (下面三个值任意一个) //
'application/x-www-form-urlencoded' // 键值对形式数据?key1=value1&key2=value2
'multipart/form-data' // 表单数据
'text/plain' // 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符。

简单请求

** 当发生浏览器跨域的简单请求时,浏览器会为当前请求自动添上一个Origin的header字段,表示请求的数据源(协议,域名,端口号Origin:http://killbugs.com:8090)**

下图为同源访问:http://localhost:3000/public/build/aaa.html 访问http://localhost:3000/public/build/api/getList,注意观察request的header里面是没有Origin字段的。

下图为非同源访问:http://localhost:3000/public/build/aaa.html 访问http://www.baidu.com/,注意观察request的header里面是有Origin字段的Origin: http://localhost:3000

简单请求总结:上面两张图均为get方式的请求,很明显,简单请求在跨域的时候会为请求头加入Origin字段。


非简单请求

** 当发生浏览器跨域的非简单请求时: 浏览器会在发送正式请求之前增加一次options预检请求,询问服务器当前网页域名,请求方式,请求头字段是否允许。当预检请求成功,则发送正式请求,正式请求同发送简单请求一般,浏览器自动在请求头中加上Origin。** 下面是options预检请求的请求头中的几个关键字段:

  • Origin:告诉服务器请求来自哪个域名(比如:http://localhost:3000)
  • Access-Control-Request-Method:必有字段,告诉服务器跨域将要使用的请求方式(是post还是什么其他的请求方式)
  • Access-Control-Request-Headers:可能有字段,告诉服务器跨域将要发送的额外头信息字段(比如说你在发送请求之前设置了 xhr.setRequestHeader('content-type', 'application/json; charset=UTF-8'),那么这个字段就会出现在请求头中Access-Control-Request-Headers: content-type

非简单请求之预检请求失败

下面代码是http://localhost:3000/public/build/aaa.htmlhttp://www.killbugs.com/的跨域非简单请求,因为设置了content-type:application/json; charset=UTF-8

let xhr = new XMLHttpRequest()
xhr.open('post', 'http://www.killbugs.com');
xhr.setRequestHeader('content-type', 'application/json; charset=UTF-8');
xhr.send('123')
xhr.onreadystatechange = function () {
    if (xhr.readyState == 4 && xhr.status == 200) {
        console.log(xhr.responseText);
    }
}

下图是该请求报文。

通过请求报文可以看到,在发送post请求之前会发送预检请求。请求方式options。但是这里的预检请求被拒绝了,返回状态码405,所以第二次的正式请求无法继续。

非简单请求之预检请求成功

下面代码是file:///Users/F181372/Documents/study/sss.htmlhttp://localhost:3000/public/build/aaa.html的跨域非简单请求,也是因为设置了content-type:application/json; charset=UTF-8

 let xhr = new XMLHttpRequest()
  xhr.open('post', 'http://localhost:3000/public/build/aaa.html');
  xhr.setRequestHeader('content-type', 'application/json; charset=UTF-8');
  xhr.send('123')
  xhr.onreadystatechange = function () {
      if (xhr.readyState == 4 && xhr.status == 200) {
          console.log(xhr.responseText);
      }
  }

下图是该请求报文。

通过请求报文可以看到,在发送post请求之前会发送预检请求。请求方式options。但是这里的预检返回状态码200,预检请求成功,所以第二次的正式请求可以继续,第二次请求报文见下图。

我们可以看到,第二次请求报文,与简单请求一致,请求头中加上了Origin字段。

服务器:服务器端处理简单请求、非简单请求

服务器端的处理是根据请求头中的Origin,Access-Control-Request-Method,Access-Control-Request-Headers等去判断服务器如何响应,如何设置响应头中的字段,在讲解服务器处理跨域请求之前,先介绍一下请求header与响应response中的一些字段含义。

请求头中的字段

  • Origin:告诉服务器请求来自哪个源(协议+域名+端口号,例:http://baidu.com:8080

  • Access-Control-Request-Method: 非简单请求时,预检请求告诉服务器正式请求时请求方式,(在预检请求时期告诉服务器我下一步正式请求将会以什么方式请求你,delete,post还是什么方式)。

  • Access-Control-Request-Headers: 非简单请求时,预检请求告诉服务器正式请求会使用到的请求头字段,(在预检请求时期告诉服务器下一步正式请求将会有什么请求头字段,比如使用POST请求,但是content-type设置了application/json,那么这个Access-Control-Request-Headers值就是content-type)。

响应头中的字段

  • Access-Control-Allow-Origin:服务器允许访问的Origin,可以是某一具体的Origin,也可以是 * ,表示来自任意Origin的访问。
  • Access-Control-Allow-Credentials:服务器端是否允许发送客户端cookie,默认无该字段(不允许客户端发送cookie),需要客户端发送cookie,设置为true,客户端跨域默认不发送cookie,客户端需要发送需要设置类似于xhr.Credentials = ture
  • Access-Control-Allow-Methods :服务器端跨域支持的请求方法。
  • Access-Control-Allow-Headers :服务器端跨域支持的请求头部字段。
  • Access-Control-Max-Age:服务器端缓存预检请求多长时间,在此期间,非简单请求不需要再次发送预检请求。以秒为单位。
  • Access-Control-Expose-Headers:告诉浏览器除了默认的响应头字段,还有哪些字段可以返回。

**注意:Access-Control-Allow-Credentials设置为true的时候,Access-Control-Allow-Origin不能设置为*,必须指定具体Origin,不需要浏览器发送cookie,则删除这个字段。 **

服务器CORS处理简单请求

  • 1,设置响应头字段Access-Control-Allow-Origin(必选),该字段用于告诉浏览器当前服务器所支持访问的源,只有匹配该字段的源才允许访问资源,可以通过请求头中的Origin设置具体某一源字段(Access-Control-Allow-Origin:http://abcd.com:8090),也可以设置*代表任意源都可以访问(Access-Control-Allow-Origin:*)。

  • 2, 设置响应头字段Vary:Origin(可选),该字段主要为了处理CDN缓存问题,为不同源缓存不同资源。
    举个例子:当前服务器对http://a.com:8080http://a.com:8090两个源需要进行跨域请求做出回应,
    按道理说对http://a.com:8080的响应头设置应该是Access-Control-Allow-Origin:http://a.com:8080,
    http://a.com:8090的响应头设置应该是Access-Control-Allow-Origin:http://a.com:8090
    但是由于缓存,http://a.com:8090的响应头返回的Access-Control-Allow-Origin:http://a.com:8080,导致http://a.com:8090的跨域请求失败。
    这时候设置了Vary:Origin,就可以为不同源缓存不同资源,解决了CDN缓存问题。

  • 3,设置响应头字段Access-Control-Allow-Credentials(可选),设置该字段表示服务器是否允许使用缓存。

    • 如果浏览器请求中的credentials为true,意味着浏览器期望使用cookie,服务器此时需要设置Access-Control-Allow-Credentials:true,表示允许使用Cookie。

    注意:当服务器配置Access-Control-Allow-Origin:*,服务器得删除Access-Control-Allow-Credentials字段(意味着不使用缓存,与此同时,我们也看到Access-Control-Allow-Credentials只可以设置true,要么就是删除该字段),即使浏览器请求中携带credentials。至于为什么,有兴趣的朋友可以深入一下

  • 4,设置响应头字段Access-Control-Expose-Headers(可选),如果服务器除了显示6个默认的响应头字段(Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma)还希望显示其他头部信息,就可以通过该字段设置。

处理简单请求基本就是以上几步操作,通过服务器端的这些配合,浏览器便可以完成跨域请求

服务器CORS处理非简单请求

  • 1,设置响应头字段Access-Control-Allow-Origin(必选)原理同简单请求

  • 2, 设置响应头字段Vary:Origin(可选)原理同简单请求

  • 3,判断请求头中是否有Access-Control-Request-Method字段,如果没有则跨域请求失败

  • 4,设置响应头字段Access-Control-Allow-Credentials(可选)原理同简单请求

  • 5,设置响应头字段Access-Control-Expose-Headers(可选),如果服务器除了显示6个默认的响应头字段(Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma)还希望显示其他头部信息,就可以通过该字段设置。

  • 6,设置响应头字段Access-Control-Allow-Methods(可选),该字段告诉浏览器服务器允许的的请求方式。

  • 7,设置响应头字段Access-Control-Allow-Headers(可选),该字段告诉浏览器是服务器允许的的请求头部字段有哪些。

**处理非简单请求基本就是以上几步操作,通过服务器端的这些配合,浏览器便可以完成跨域请求,

总结

此处服务器对两种请求的处理方式,可以直接阅读 koa2中的cors处理代码,这是服务器cors源码,源码其实很简单,上面服务器的处理方式其实就是根据源码总结出来的,强烈推荐阅读。

最后,服务器端的配合浏览器对跨域时候的特殊处理,使得跨域问题得到解决,其实前端不需要做任何事(使用cookie还是得前端配合),基本靠服务器端处理就OK。

本文部分参考:CORS原理及@koa/cors源码解析

3083