CORS:一种基于HTTP头的机制,该机制允许服务标示除了它自己以外的其他源(域、协议、端口),使得浏览器允许这些源访问加载自己的资源

对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(例如 CookieHTTP 认证相关数据)。

若请求满足所有下述条件,则该请求可视为简单请求

  • 使用下列方法之一:

  • 除了被用户代理自动设置的标头字段(例如

    ConnectionUser-Agent或其他在 Fetch 规范中定义为禁用标头名称的标头),允许人为设置的字段为 Fetch 规范定义的对 CORS 安全的标头字段集合

    。该集合为:

  • Content-Type标头所指定的媒体类型的值仅限于下列三者之一:

    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  • 如果请求是使用 XMLHttpRequest 对象发出的,在返回的 XMLHttpRequest.upload 对象属性上没有注册任何事件监听器;也就是说,给定一个 XMLHttpRequest 实例 xhr,没有调用 xhr.upload.addEventListener(),以监听该上传请求。

  • 请求中没有使用 ReadableStream 对象。

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

Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: https://foo.example

当响应的是附带身份凭证的请求时,服务端必须明确 Access-Control-Allow-Origin 的值,而不能使用通配符“*

预检请求

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

img

下面是服务端和客户端完整的信息交互。首次交互是预检请求/响应

OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
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
Connection: keep-alive
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://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
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive


从上面的报文中,我们看到,第 1 - 10 行使用 OPTIONS 方法发送了预检请求,浏览器根据上面的 JavaScript 代码片断所使用的请求参数来决定是否需要发送,这样服务器就可以回应是否可以接受用实际的请求参数来发送请求。OPTIONS 是 HTTP/1.1 协议中定义的方法,用于从服务器获取更多信息,是安全的方法。该方法不会对服务器资源产生影响。注意 OPTIONS 预检请求中同时携带了下面两个标头字段:

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
标头字段 Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法。标头字段 Access-Control-Request-Headers 告知服务器,实际请求将携带两个自定义请求标头字段:X-PINGOTHERContent-Type。服务器据此决定,该实际请求是否被允许。

在响应附带身份凭证的请求时:

  • 服务器不能Access-Control-Allow-Origin 的值设为通配符“*”,而应将其设置为特定的域,如:Access-Control-Allow-Origin: https://example.com
  • 服务器不能Access-Control-Allow-Headers 的值设为通配符“*”,而应将其设置为标头名称的列表,如:Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
  • 服务器不能Access-Control-Allow-Methods 的值设为通配符“*”,而应将其设置为特定请求方法名称的列表,如:Access-Control-Allow-Methods: POST, GET

Http响应标头字段

Access-Control-Allow-Origin

Access-Control-Allow-Origin: <origin> | *

该字段的值为通配符“*”,表示允许来自任意源的请求

如果服务端指定了具体的单个源(作为允许列表的一部分,可能会根据请求的来源而动态改变)而非通配符“*”,那么响应标头中的 Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的 Origin 返回不同的内容。

Access-Control-Allow-Origin: https://mozilla.org
Vary: Origin

Access-Control-Expose-Headers

跨源访问时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。

Access-Control-Expose-Headers: <header-name>[, <header-name>]*

Access-Control-Allow-Credentials

头指定了当浏览器的 credentials 设置为 true 时是否允许浏览器读取 response 的内容。当用在对 preflight 预检测请求的响应中时,它指定了实际的请求是否可以使用 credentials。请注意:简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。

Access-Control-Allow-Credentials: true

Access-Control-Allow-Methods

Access-Control-Allow-Methods 标头字段指定了访问资源时允许使用的请求方法,用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。

Access-Control-Allow-Methods: <method>[, <method>]*

Access-Control-Allow-Headers

Access-Control-Allow-Headers 标头字段用于预检请求的响应。其指明了实际请求中允许携带的标头字段。这个标头是服务器端对浏览器端 Access-Control-Request-Headers 标头的响应。

Access-Control-Allow-Headers: <header-name>[, <header-name>]*

Http请求标头字段

Origin

Origin 标头字段表明预检请求或实际跨源请求的源站。

Origin: <origin>
origin 参数的值为源站 URL。它不包含任何路径信息,只是服务器名称。
origin 的值可以为 null。
在所有访问控制请求中,Origin 标头字段总是被发送。

Access-Control-Request-Method

Access-Control-Request-Method 标头字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。

Access-Control-Request-Method: <method>

Access-Control-Request-Header

Access-Control-Request-Headers 标头字段用于预检请求。其作用是,将实际请求所携带的标头字段(通过 setRequestHeader() 等设置的)告诉服务器。这个浏览器端标头将由互补的服务器端标头 Access-Control-Allow-Headers 回答。

Access-Control-Request-Headers: <field-name>[, <field-name>]*