CORS

2018-12-11

什么是跨域请求 ?

当一个资源从另外一个与该资源有不同域名(Domain),协议(protocal),端口(port)的服务器请求资源时,所发出的请求即为跨域请求。

由于某些安全因素,浏览器限制跨域请求,XMLHttpRequestFetch都遵循同源策略。

哪些请求涉及CORS

  • 刚刚提到的通过XMLHttpRequestFetch进行的跨域请求
  • 请求涉及 Web Fonts
  • 通过 canvas drawImage()进行构图的图片或者视频

什么是跨域资源共享 ?

CORS is the mechanism that use additional HTTP headers to tell a browser to let a web application running at one origin(domani) have permission to access selected resources from a server at a different.

跨域资源共享是一种机制,通过设置额外的HTTP头部来授权不同域名的网站或者Web app通过HTTP请求来本网站的选定内容。

也就是说通过CORS, 我们可以在服务器端设置谁可以访问我们的资源。

而且对于一些有副作用的请求,CORS要求浏览器进行”预检验“。浏览器首先向服务器发送OPTIONS请求,浏览器获得服务器允许后会真正的请求。

跨域请求失败后, JavaScript代码是无法直接了解到错误信息的, 只有在console才能看到报错信息。

CORS 处理的三种请求

简单请求

有些请求不会触发CORS预检验,这些所谓的简单请求要满足以下所有条件:

  • 使用GET, HEAD, POST方法
  • 除了浏览器自动设置的请求头部,和下列请求头部之外又设置了自定义头部的请求。
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • DPR
    • DownLink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type为以下值的请求:
    • applicaton/x-www-form-urlencoded
    • multipart/form-data
    • text/palin
  • XMLHttpRequestUpload对象上没有注册时间监听的请求
  • 没有使用任何ReadableStream对象的请求

    一个简单请求的处理过程
    xiao

    对应的HTTP请求与响应

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
      GET /resources/public-data/ HTTP/1.1
    Host: bar.other
    User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Connection: keep-alive
    Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
    Origin: http://foo.example


    HTTP/1.1 200 OK
    Date: Mon, 01 Dec 2008 00:23:53 GMT
    Server: Apache/2.0.61
    Access-Control-Allow-Origin: *
    Keep-Alive: timeout=2, max=100
    Connection: Keep-Alive
    Transfer-Encoding: chunked
    Content-Type: application/xml

    [XML Data]

请求中Origin表明了该请求由哪个域名发出,而在服务器的响应中设定了Access-Control-Allow-Origin头部, 其值为*表示请求的Origin为任何值都可以访问,如果设置Access-Control-Allow-Origin的值为http://foo.example则只有来自于http://foo.example的请求可以访问对应资源。

预检验请求


由于预检验请求可能会影响用户数据,预检验请求需要先向服务器发送OPTIONS请求来征求服务器同意。

满足下列任意条件之一的请求都需要预检验:

  • 使用如下方法:
    • PUT
    • DELETE
    • CONNECT
    • OPTIONS
    • TRACE
    • PATCH
  • 除了浏览器自动设置的头部,和以下头部外,包括其他自定义头部:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • DPR
    • DownLink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type设置为除了以下值的其他值:
    • applicaton/x-www-form-urlencoded
    • multipart/form-data
    • text/palin
  • XMLHttpRequestUpload对象上注册时间监听的请求
  • 使用任何ReadableStream对象的请求

发送一个预检验请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const invocation = new XMLHttpRequest();
const url = 'http://bar.other/resources/post-here/';
const body = '<?xml version="1.0"?><person><name>Arun</name></person>';

function callOtherDomain(){
if(invocation)
{
invocation.open('POST', url, true);
invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
invocation.setRequestHeader('Content-Type', 'application/xml');
invocation.onreadystatechange = handler;
invocation.send(body);
}
}

该请求通过POST方法发送XML格式数据,并设置了自定义头部X-PINGOTHER:pingpong,所有需要预检验。

这个预检验流程
预检验请求与响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

Access-COntrol-Request-Method:POST告知服务器真实请求的请求方法,而Access-Control-Request-Headers: X-PINGOTHER, Content-Type则告知真实请求会发送的自定义头部。

1
2
3
4
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

服务器通过以上响应头部告知浏览器服务器所接受的方法,域名,头部和缓存时间。

发送凭证的请求

默认情况下,跨域的XMLHttpRequestFetch调用不会发送凭证。需要特别设置flag才能发送。

跨域发送Cookies

1
2
3
4
5
6
7
8
9
10
11
const invocation = new XMLHttpRequest();
const url = 'http://bar.other/resources/credentialed-content/';

function callOtherDomain(){
if(invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
}

设置withCredentials值为true后可以发送Cookies, 由于是简单的GET请求,所以不会进行预检验。但是如果响应中不包含Aceess-Control-Allow-Credentials:ture的话浏览器不会显示响应内容。

请求与相应流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain


[text/plain payload]

当服务器响应带有凭证的跨域请求的时候, 需要在响应的Access-Control-Allow-Origin中指定域名,如果只设置为*通配符的话,请求会失败。