
Node.js 后端开发指南:搭建、优化与部署
有关 REST API 跨域资源共享 (CORS) 的深入指南,包括 CORS 的工作原理以及常见陷阱(尤其是在安全性方面)。
CORS 是一种安全机制,允许来自一个域或源的网页访问具有不同域的资源(跨域请求)。 CORS 是现代浏览器中实现的同源策略的放宽。如果没有 CORS 之类的功能,网站就只能通过所谓的同源策略访问来自同一源的资源。
和许多网站一样,您可能会使用 Cookie 来跟踪身份验证或会话信息。这些 Cookie 在创建时会绑定到某个域。每次对该域进行 HTTP 调用时,浏览器都会附加为该域创建的 Cookie。每次HTTP 调用都是如此,可能是针对静态图像、HTML 页面,甚至是 AJAX 调用。
这意味着当您登录https://examplebank.com时,会为https://examplebank.com存储一个 cookie 。如果该银行是单页 React 应用程序,他们可能已在https://examplebank.com/api创建 REST API ,以便 SPA 通过 AJAX 进行通信。
假设你 登录https://examplebank.com后浏览到一个恶意网站https://evilunicorns.com。如果没有同源策略,那么黑客网站可以对https://examplebank.com/api进行经过身份验证的恶意 AJAX 调用,即使黑客网站无法直接访问银行的 cookie。POST /withdraw
这是因为浏览器会自动将与https://examplebank.com绑定的任何 cookie 附加到该域的任何 HTTP 调用,包括从https://evilunicorns.com到https://examplebank.com 的AJAX 调用。通过将 HTTP 调用限制为仅来自同一来源(即浏览器选项卡的域)的调用,同源策略可以关闭一些黑客后门,例如跨站点请求伪造(CSRF)(尽管不是全部。CSRF 令牌等机制仍然是必要的)。
来源包括协议、域和端口的组合。这意味着https:// api .mydomain.com和https://mydomain.com实际上是不同的来源,因此受到同源策略的影响。类似地,http://localhost: 9000和http://localhost: 8080也是不同的来源。考虑来源时会忽略路径或查询参数。
来源是指发起请求的内容,通常是打开的浏览器选项卡,但也可能是 iFrame 窗口的来源。
网站发出跨域 HTTP 请求是有正当理由的。也许https://mydomain.com上的单页应用需要对https://api.mydomain.com进行 AJAX 调用;或者也许https://mydomain.com包含一些第三方字体或分析提供商,如 Google Analytics 或 MixPanel。 跨域资源共享(CORS) 支持这些跨域请求。
CORS 请求有两种类型:简单请求和预检请求。关于请求是否预检的规则将在后面讨论。
简单请求是在发起之前不需要预检请求(初步检查)的 CORS 请求。
https://www.mydomain.com
发起 AJAX 请求GET https://api.mydomain.com/widgets
Host
,浏览器还会自动添加Origin
跨源请求的请求标头:GET /widgets/ HTTP/1.1
Host: api.mydomain.com
Origin: https://www.mydomain.com
[Rest of request...]
Origin
请求标头。如果 Origin 值被允许,则将 设置Access-Control-Allow-Origin
为请求标头中的值Origin
。HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.mydomain.com
Content-Type: application/json
[Rest of response...]
Access-Control-Allow-Origin
则检查通过(如本例所示) 。Access-Control-Allow-Origin
响应的服务器Access-Control-Allow-Origin: *
允许所有来源,这可能带来很大的安全风险。
仅当您的应用程序绝对需要它(例如创建开放/公共 API)时才使用 *。
可以看到,服务器可以根据请求的来源来决定是否允许该请求。浏览器保证Origin
请求标头的设置可靠且准确。
预检请求是另一种类型的 CORS 请求。预检请求是一种 CORS 请求,其中浏览器需要在发送预检请求之前发送预检请求(即初步探测),以询问服务器是否可以继续执行原始 CORS 请求。此预检请求本身是OPTIONS
对同一 URL 的请求。
由于原始 CORS 请求之前有一个预检请求,因此我们将原始 CORS 请求称为已预检。
来源: Mozilla
Content-Type
标头是application/json
Authorization
这意味着,支持单页应用程序的 REST API 通常可以对大多数 AJAX 请求进行预检。
https://www.mydomain.com
启动一个POST https://api.mydomain.com/widgets
带有 JSON 有效负载的经过身份验证的 AJAX 请求。浏览器OPTIONS
首先发送请求(又称为预检请求),其中包含主请求的建议请求方法和请求标头:OPTIONS /widgets/ HTTP/1.1
Host: api.mydomain.com
Origin: https://www.mydomain.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization, Content-Type
[Rest of request...]
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.mydomain.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Content-Type: application/json
[Rest of response...]
Origin
此请求中也包含标头。POST /widgets/ HTTP/1.1
Host: api.mydomain.com
Authorization: 1234567
Content-Type: application/json
Origin: https://www.mydomain.com
[Rest of request...]
Access-Control-Allow-Origin
标头中具有正确的来源,因此检查通过并将控制权交还给浏览器选项卡。HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.mydomain.com
Content-Type: application/json
[Rest of response...]
您已经在前面的示例中看到了一些用于 CORS 的标头,例如Access-Control-Allow-Origin
和Access-Control-Allow-Methods
,但还有更多标头可用于更精细的控制。以下是控制 CORS 的标头的完整列表。
标头名称 | 示例值 | 描述 | 用于预检请求 | 用于 CORS 请求 |
---|---|---|---|---|
起源 | https://www.mydomain.com | 打开的浏览器选项卡的协议、域和端口组合 | 是的 | 是的 |
访问控制请求方法 | 邮政 | 对于预检请求,指定原始 CORS 请求将使用的方法 | 是的 | 不 |
访问控制请求标头 | 授权,X-PING | 对于预检请求,逗号分隔的列表指定原始 CORS 请求将发送的标头 | 是的 | 不 |
当前浏览器尚未完全实现Access-Control-Allowed-Headers、Access-Control-Allow-Methods 和 Access-Control-Expose-Headers上的通配符 (*) 。
最好列出标头或方法。
CORS 是在尝试保持安全性的同时放宽同源策略的一种方式。使用 * 会禁用 CORS 的大多数安全规则。有些情况下可以使用通配符,例如集成到许多第三方网站的开放 API。
您可以通过将 API 放在不同的域上来提高安全性。
例如,您的开放 API https://api.mydomain.com可以响应Access-Control-Allow-Origin: *
,但您主网站的 API https://www.mydomain.com/api仍然响应Access-Control-Allow-Origin: https://www.mydomain.com
不幸的是,规范不允许Access-Control-Allow-Origin: https://mydomain.com, https://www.mydomain.com
。服务器只能使用一个域或 * 进行响应,但您可以利用Origin
请求标头。
这不是 CORS 规范的一部分,通配符只能用于表示允许所有域。
Access-Control-Allow-Origin: mydomain.com
由于未包含协议,因此无效。
类似地,Access-Control-Allow-Origin: http://localhost
除非服务器实际上在标准 HTTP 端口上运行,否则您将遇到麻烦:80
。
大多数 CORS 框架都会自动执行此操作,您必须向客户端指定服务器响应将根据请求来源而有所不同。
如果未包含必需的标头,CORS 请求仍将通过,但未列入白名单的响应标头将在浏览器选项卡中隐藏。CORS 请求始终公开的默认响应标头是:
这是一个让很多人困惑的棘手案例。如果响应包含Access-Control-Allow-Credentials: true
,则通配符运算符不能用于任何响应标头,例如 Access-Control-Allow-Origin。
文章来源:Authoritative guide to CORS (Cross-Origin Resource Sharing) for REST APIs