跨源资源共享(CORS)
2021.11.24 Wed

跨源资源共享 (Cross-Origin Resource Sharing)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的“预检”请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。

例如,运行在 localhost 端口的单应用框架在请求后端接口时,如果该接口的服务器没有标示 localhost 地址,就会产生跨域问题。

功能概述

跨源资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。

简单请求

某些请求不会触发 CORS 预检请求,称之为“简单请求”。若请求满足所有下述条件,则该请求可视为“简单请求”:

  • 使用下列方法之一:
    GET
    HEAD
    POST
  • 手动设置的头部字段仅包含以下(同时 header 的 value 长度要满足小于 128)
    Accept
    Accept-Language
    Content-Language
    Content-Type (需要注意额外的限制)
  • Content-Type 的值仅限于下列三者之一:
    text/plain
    multipart/form-data
    application/x-www-form-urlencoded

例如站点 http://foo.example 的网页应用访问 http://bar.other 的资源

1
2
3
4
5
6
7
# 请求头
Origin: http://foo.example


# 响应头
Access-Control-Allow-Origin: *
Content-Type: application/xml

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

预检请求

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

例如,现在通过 get 方法从服务器发送一个需要预检的请求

预检请求 OPTIONS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 请求头
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Access-Control-Request-Headers: authorization,content-type
Access-Control-Request-Method: GET
Origin: http://localhost:4200
Referer: http://localhost:4200/
Sec-Fetch-Mode: cors


#响应头
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN, X-Requested-With, enctype
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, OPTIONS
Access-Control-Allow-Origin: http://localhost:4200
Access-Control-Expose-Headers: Authorization
Access-Control-Max-Age: 86400
Allow: GET,HEAD
Content-Type: text/html; charset=UTF-8

  • 请求头中
    首部字段 Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法。

    首部字段 Access-Control-Request-Headers 告知服务器,实际请求将携带两个自定义请求首部字段:X-PINGOTHER 与 Content-Type。服务器据此决定,该实际请求是否被允许。

  • 响应头中
    首部字段 Access-Control-Allow-Methods 表明服务器允许客户端使用 POST, GET 和 OPTIONS 等方法发起请求。该字段与 HTTP/1.1 Allow: response header 类似,但仅限于在需要访问控制的场景中使用。

    首部字段 Access-Control-Allow-Headers 表明服务器允许请求中携带字段 Authorization。

    最后,首部字段 Access-Control-Max-Age 表明该响应的有效时间为 86400 秒,也就是 24 小时。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

实际请求 GET

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#请求头
Accept: application/json
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
Content-Type: application/json
Origin: http://localhost:4200
Referer: http://localhost:4200/



#响应头
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN, X-Requested-With, enctype
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, OPTIONS
Access-Control-Allow-Origin: http://localhost:4200
Access-Control-Expose-Headers: Authorization
Content-Encoding: gzip
Content-Type: application/json
Vary: Accept-Encoding

  • 请求头中
    首部字段 Authorization 为自定义的首部字段

withCredentials 属性

CORS 请求默认不发送 Cookie 和 HTTP 认证信息。如果要把 Cookie 发到服务器,一方面要服务器同意,指定 Access-Control-Allow-Credentials 字段,Access-Control-Allow-Credentials: true。另一方面,开发者必须在 AJAX 请求中打开 withCredentials 属性。

1
2
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

否则,即使服务器同意发送 Cookie,浏览器也不会发送。或者,服务器要求设置 Cookie,浏览器也不会处理。
但是,如果省略 withCredentials 设置,有的浏览器还是会一起发送 Cookie。这时,可以显式关闭 withCredentials,xhr.withCredentials = false;

需要注意的是,如果要发送 Cookie,Access-Control-Allow-Origin 就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨源)原网页代码中的 document.cookie 也无法读取服务器域名下的 Cookie。

HTTP 响应首部字段

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

  • Access-Control-Expose-Headers
    在跨源访问时,XMLHttpRequest 对象的 getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。
    让服务器把允许浏览器访问的头放入白名单
    Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
    这样浏览器就能够通过 getResponseHeader 访问 X-My-Custom-Header 和 X-Another-Custom-Header 响应头了。

  • Access-Control-Max-Age
    Access-Control-Max-Age: <delta-seconds>
    指定了 preflight 请求的结果能够被缓存多久,delta-seconds 参数表示 preflight 请求的结果在多少秒内有效。

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

  • Access-Control-Allow-Methods
    于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。

  • Access-Control-Allow-Headers
    用于预检请求的响应。其指明了实际请求中允许携带的首部字段。

HTTP 请求首部字段

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

  • Origin 表明预检请求或实际请求的源站。
  • Access-Control-Request-Method 用于预检请求,其作用是,将实际请求所使用的 HTTP 方法告诉服务器。
  • Access-Control-Request-Headers 用于预检请求,其作用是,将实际请求所携带的首部字段告诉服务器。

参考链接:
MDN-跨源资源共享

检测到页面内容有更新,是否刷新页面