OPTIONS 方法不只是 CORS 预检

工具相关 ·

一个被忽视的方法

提到 OPTIONS,大多数人的第一反应是「浏览器跨域时自动发的那玩意儿」。确实,CORS 预检让 OPTIONS 成了最容易被开发者忽略的方法——它由浏览器自动发出,开发者通常只关心它返回的 Access-Control-Allow-* 头对不对。

但 OPTIONS 本身的用途比预检更宽:它的定义是「查询目标资源在通信中可用的选项」,也就是「我不确定这个地址能用什么方式访问,先问一句」。

用 OPTIONS 探测接口支持哪些方法

拿到一个不熟悉的接口,不确定它允许哪些方法,直接发一个 OPTIONS 请求就行:

OPTIONS /api/users HTTP/1.1
Host: example.com

服务端会在 Allow 响应头里列出全部可用方法:

HTTP/1.1 204 No Content
Allow: GET, POST, PUT, DELETE, OPTIONS

比起靠文档或者试错,这个方法更直接。写接口调试脚本、排查「为什么 PUT 返回 405」这类问题时特别有用——405 Method Not Allowed 的响应里通常也会带 Allow 头,但主动问一次能省下不少来回。

顺带说一句,HEAD 是另一个方向的补充:它回答「这个资源现在是什么状态」(存在与否、大小、类型、最后修改时间),而 OPTIONS 回答「我能对它做什么」。两个方法都不返回响应体,开销极小。

通配符 OPTIONS 请求

除了针对具体地址的 OPTIONS,还有一个特殊形式:

OPTIONS * HTTP/1.1
Host: example.com

请求目标是 * 而不是某个路径,含义是「询问整台服务器支持哪些方法」。实际中很少有服务器实现这个形式,返回 200 或 400 都常见,因此不建议依赖它。针对具体路径的 OPTIONS 才是可靠的用法。

CORS 预检是怎么用它工作的

浏览器对「复杂请求」会自动先发一个预检请求。所谓复杂,指的是满足下面任意一条:

  • 方法不是 GET、HEAD、POST;
  • 请求头里带了非简单头(例如 Content-Type: application/json、自定义头、Authorization);
  • Content-Type 的值不是 application/x-www-form-urlencoded、multipart/form-data、text/plain 三者之一。

预检请求会带上三个关键头:

OPTIONS /api/orders HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, authorization

服务端据此判断是否放行,通过则返回允许的来源、方法、请求头与预检结果缓存时长:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

预检通过后,浏览器才会发送真正的业务请求。

预检相关的三个常见坑

每次请求都发预检,白白多一个往返。 原因是 Access-Control-Max-Age 没设,或者设了但浏览器不认。缓存时长按秒计,常见取值是 600 到 86400。注意 Chrome 对该值有自己的上限,设得过长不会完全生效。

自定义请求头拼写不一致。 Access-Control-Allow-Headers 里的头名必须覆盖 Access-Control-Request-Headers 里的全部条目,少一个就预检失败。常见错误是漏了 Authorization,导致所有带登录态的请求全部跨域报错。

凭证请求不能用通配符。 当请求带 credentials(Cookie、HTTP 认证)时,Access-Control-Allow-Origin 必须是具体的来源地址,不能写 *;同理 Access-Control-Allow-Headers 和 Allow-Methods 也建议逐一列出。这是最常见的 CORS 配置错误。

生产环境的两个安全建议

别把 OPTIONS 全站放开。 无差别地对所有路径返回允许一切来源、一切方法,等于把跨域策略形同虚设。按路由精确配置更安全。

注意 OPTIONS 可能绕过鉴权。 如果鉴权中间件对 OPTIONS 请求直接放行,而业务路由又真的实现了 OPTIONS 处理逻辑,就可能出现未授权访问。稳妥做法是让预检请求只返回 CORS 相关响应头,不暴露业务信息。

检查清单

  • 接口的 Allow 头是否与实际支持的方法一致
  • 预检响应是否设置了合理的 Access-Control-Max-Age
  • Access-Control-Allow-Headers 是否覆盖了客户端实际发送的全部自定义头
  • 带凭证的请求是否避开了通配符 *
  • OPTIONS 处理是否只返回 CORS 头,没有泄漏业务数据
阅读 13