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