跨域资源共享(CORS)

跨域资源共享(CORS)

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能(IE浏览器不能低于IE10),因此只要服务器实现了CORS接口,就可以跨源通信。

什么情况下需要 CORS ?

跨域资源共享标准( cross-origin sharing standard )允许在下列场景中使用跨域 HTTP 请求:

  • 由 XMLHttpRequest 或 Fetch 发起的跨域 HTTP 请求。
  • Web 字体 (CSS 中通过 @font-face 使用跨域字体资源), 因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。
  • WebGL 贴图
  • 使用 drawImage 将 Images/video 画面绘制到 canvas
  • 样式表(使用 CSSOM)

两种请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
若请求同时满足下述条件,则该请求可视为“简单请求”:

  1. 请求方法是以下三种方法之一:
    • HEAD:类似GET请求,但只请求页面的首部。
    • GET:请求指定的页面信息,并返回实体主体。
    • POST:请求服务器接受所指定的文档作为对所标识的URI的新的从属实体。
  2. HTTP的头信息不超出Fetch 规范的 CORS 安全的首部字段集合:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (需要注意额外的限制)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  3. Content-Type 的值仅限于下列三者之一:
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  4. 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
  5. 请求中没有使用 ReadableStream 对象。

凡是不同时满足上面条件,就属于非简单请求。浏览器对这两种请求的处理,是不一样的。

简单请求(simple request)

“简单请求”请求不会触发 CORS 预检请求。
下面例子假设 http://foo.example 的网页应用想要访问 http://bar.other 的资源

# 请求报文
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
  • 请求首部字段 Origin 表示请求的来源地址
  • 响应首部字段 Access-Control-Allow-Origin 表示允许的请求的来源地址,*表示允许任意外域访问。通过该字段,可以指定允许访问的来源地址,如:Access-Control-Allow-Origin: http://foo.example

注:简单请求可以通过指定 * 来允许任意外域访问,但是非简单请求将会抛出异常。需要对它指定明确的地址。

非简单请求(not-so-simple request)

不满足上述条件的请求的都属于非简单请求。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。先看如下非简单请求

# 请求报文
POST /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
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache

# 预期得到的响应报文,在这之前会发起一个 `OPTIONS` 的"预检请求"
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
  • 请求报文的首部字段存在一个自定义 X-PINGOTHER 字段
  • Content-Type 的值与之前约定的三个值都不符。

不满足简单请求,浏览器把其当作非简单请求处理,并发送一个预检请求,以获知服务器是否允许该实际请求。

预检请求

非简单请求都必须首先使用 OPTIONS 方法发起一个 “预检请求” 到服务器,以获知服务器是否允许该实际请求。 “预检请求” 的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

# 预检请求
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
  • 服务器收到 “预检请求” 以后,检查 OriginAccess-Control-Request-MethodAccess-Control-Request-Headers 字段以确认允许跨源请求。
  • Access-Control-Request-Headers 字段保存了所有请求时的自定义首部字段,以逗号分隔。
  • 预检请求响应存在 Access-Control-Max-Age 字段用来指定本次预检请求的有效期,单位为秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

    关于Access-Control-Max-Age的最大有效时间,请参考文章 HTTP-CORS Access-Control-Max-Age被忽略

附带身份凭证的请求

CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器:

  • 前端需在请求首部字段设置withCredentials=true
  • 服务器也要指定 Access-Control-Allow-Credentials=true

对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“*”。

  • 请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“*”,请求将会失败。而将 Access-Control-Allow-Origin 的值设置为请求的源地址(如果发起的域名为http://foo.example,设置Access-Control-Allow-Origin=http://foo.example即可 ),则请求将成功执行。
  • 响应首部如果携带了 Set-Cookie 字段,尝试对 Cookie 进行修改。如果操作失败,将会抛出异常。

总结HTTP首部字段

请求首部字段

本节列出了可用于发起跨域请求的首部字段。请注意,这些首部字段无须手动设置。 当开发者使用 XMLHttpRequest 对象发起跨域请求时,它们已经被设置就绪。

  • Origin:该字段表明预检请求或实际请求的源站。(参数的值为源站 URI。它不包含任何路径信息,只是服务器名称。)

    有时候将该字段的值设置为空字符串是有用的,例如,当源站是一个 data URL 时。

  • Access-Control-Request-Method: 用于预检请求,将实际请求所使用的 HTTP 方法告诉服务器。
  • Access-Control-Request-Headers:用于预检请求,将实际请求所携带的首部字段告诉服务器。

响应首部字段

  • Access-Control-Allow-Origin:指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。

    如果服务端指定了具体的域名而非“*”,那么响应首部中的 Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的源站返回不同的内容。

  • Access-Control-Expose-Headers:在跨域访问时,只能拿到一些最基本的响应头。如果要访问其他头,则需要让服务器把允许浏览器访问的头放入白名单。
  • Access-Control-Max-Age:预检请求的结果能够被缓存多久
  • Access-Control-Allow-Credentials: 指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。

    简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。

  • Access-Control-Allow-Methods:用于预检请求的响应,指明实际请求所允许使用的 HTTP 方法。
  • Access-Control-Allow-Headers:用于预检请求的响应。其指明了实际请求中允许携带的首部字段。

参考文章