# 网络请求-CORS

# 为什么需要 CORS?

CORS 的存在是为了保护互联网免受黑客攻击。

说真的,在这说点儿题外话,讲讲它的历史。

多年来,来自一个网站的脚本无法访问另一个网站的内容。

但是 Web 开发人员需要更多功能。人们发明了各种各样的技巧去突破该限制,并向其他网站发出请求。

起初,跨源请求是被禁止的。但是,经过长时间的讨论,跨源请求被允许了,但是任何新功能都需要服务器明确允许,以特殊的 header 表述。

# 用于简单请求的 CORS

如果一个请求是跨源的,浏览器始终会向其添加 Origin header。

# 受限制的 header

对于跨源请求,默认情况下,JavaScript 只能访问“简单” response header:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma 访问任何其他 response header 都将导致 error。

要授予 JavaScript 对任何其他 response header 的访问权限,服务器必须发送 Access-Control-Expose-Headers header。它包含一个以逗号分隔的应该被设置为可访问的非简单 header 名称列表。

Access-Control-Allow-Origin: https://javascript.info
Access-Control-Expose-Headers: Content-Length,API-Key

# 非简单请求

我们可以使用任何 HTTP 方法:不仅仅是 GET/POST,也可以是 PATCH,DELETE 及其他。

之前,没有人能够设想网页能发出这样的请求。因此,可能仍然存在有些 Web 服务将非标准方法视为一个信号:“这不是浏览器”。它们可以在检查访问权限时将其考虑在内。

因此,为了避免误解,任何“非标准”请求 —— 浏览器不会立即发出在过去无法完成的这类请求。即在它发送这类请求前,会先发送“预检(preflight)”请求来请求许可。

    1. 预检请求使用 OPTIONS 方法,它没有 body,但是有两个 header:
Access-Control-Request-Method
# 带有非简单请求的方法。
Access-Control-Request-Headers
# 提供一个以逗号分隔的非简单 HTTP-header 列表。
    1. 如果服务器同意处理请求,那么它会进行响应,此响应的状态码应该为 200,没有 body,具有 header:
Access-Control-Allow-Origin
# 必须为 * 或进行请求的源(例如 https://javascript.info)才能允许此请求。
Access-Control-Allow-Methods
# 必须具有允许的方法。
Access-Control-Allow-Headers
# 必须具有一个允许的 header 列表。
header Access-Control-Max-Age
# 可以指定缓存此权限的秒数。因此,浏览器不是必须为满足给定权限的后续请求发送预检。
    1. 预检成功后,浏览器现在发出主请求。

# 凭据(Credentials)

默认情况下,由 JavaScript 代码发起的跨源请求不会带来任何凭据(cookies 或者 HTTP 认证(HTTP authentication))。 这是因为具有凭据的请求比没有凭据的请求要强大得多。如果被允许,它会使用它们的凭据授予 JavaScript 代表用户行为和访问敏感信息的全部权力。

服务器真的这么信任这种脚本吗?是的,它必须显式地带有允许请求的凭据和附加 header。

要在 fetch 中发送凭据,我们需要添加 credentials: "include" 选项,像这样:

fetch("http://another.com", {
  credentials: "include"
});

如果服务器同意接受 带有凭据 的请求,则除了 Access-Control-Allow-Origin 外,服务器还应该在响应中添加 header Access-Control-Allow-Credentials: true

200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Credentials: true

# 总结

对于简单请求:

对于没有凭据的请求(默认不发送),服务器应该设置 Access-Control-Allow-Origin 为 * 或与 Origin 的值相同 对于具有凭据的请求,服务器应该设置:

  • Access-Control-Allow-Origin 值与 Origin 的相同
  • Access-Control-Allow-Credentials 为 true

对于非简单请求 会在请求之前发出初步“预检”请求,服务器应该响应状态码为 200 和 header

  • Access-Control-Allow-Methods 带有允许的方法的列表,
  • Access-Control-Allow-Headers 带有允许的 header 的列表,
  • Access-Control-Max-Age 带有指定缓存权限的秒数。

如果设置了 Max-Age,则时间过期后,浏览器需要重新发送预检请求

下面是一个常用的允许跨域并且携带凭据的 CORS 路由(express)

app.use("", (req, res, next) => {
  res.set({
    "Access-Control-Allow-Credentials": true, // 针对带凭据的简单请求
    "Access-Control-Allow-Origin": req.headers.origin || "*", // 针对简单请求
    "Access-Control-Allow-Headers": "Content-Type,Access-Token", // 针对预检请求
    "Access-Control-Allow-Methods": "PUT,POST,GET,DELETE,OPTIONS", // 针对预检请求
    "Content-Type": "application/json; charset=utf-8"
  });
  next();
});