入门指南
取图能显示、上传却 403:预检是另一条请求,两个 403 指向两处
浏览器直传失败,往往不是权限:预检是另发的 OPTIONS,S3 用第一条匹配的规则回答,两个 403 分别指向「没配」与「没匹配」。把方法、头、暴露头与代理层拆开核对。
预检没过,真正那条 PUT 根本不会发出去。

先看答案
- 适合谁
- 把大文件从浏览器直传对象存储,控制台只报一句 CORS 错误的人
- 桶上「明明配了 CORS」,却分不清是没配好还是根本没匹配上的人
- 直连存储能传、换成自己的域名或 CDN 就失败的人
- 你会获得
- 上传失败先分清:是预检没答,还是答了没放行。
- 发布时间
- 实质更新
- 发布
- AOPC 编辑部
60 秒结论
一分钟看懂这篇文章
预检没过,真正那条 PUT 根本不会发出去。
人负责
- 判断失败发生在预检还是上传
- 决定 CORS 规则怎么写、按什么顺序写
AI 负责
- 用 curl 复现预检并读响应头
- 核对方法、头与暴露头三组清单
- 分请求
- 查规则
- 读 403
- 测代理
直接答案
「图片能显示,上传同一个桶却 403。」
这不是矛盾,是两条不同的请求。取图通常是一条不带额外头的 GET——按 Fetch 规范,GET 属于 CORS 安全列表方法,只要没有别的非安全头,浏览器根本不发预检,直接发请求、看响应里有没有 Access-Control-Allow-Origin。上传通常是 PUT,还带着 Authorization 或 Content-Type: application/json——这两样都会让浏览器先发一条 OPTIONS 预检,等它被放行,真正的 PUT 才会发出去。
所以同一个桶、同一套权限,读能成、写不能成,完全正常。要查的是预检那一条。
| 预检(preflight) | 真正的上传 | |
|---|---|---|
| 方法 | OPTIONS |
PUT(或 POST) |
| 谁发起 | 浏览器(user agent) | 你的代码,但由浏览器代发 |
| 关键请求头 | Origin、Access-Control-Request-Method、Access-Control-Request-Headers |
Origin + 真实的 Authorization/Content-Type 等 |
| 服务端怎么答 | 按第一条匹配的规则回 Access-Control-Allow-* |
正常处理,并在响应里带 Access-Control-Allow-Origin |
| 成功长什么样 | 200/204 + 对应的 Allow-* 头 |
任意状态码,只要带对 CORS 头 |
| 失败长什么样 | 没有 CORS 头,或 403 + 一句 CORS Response: 文本 |
浏览器把响应挡在 JS 之外 |
最后一行是本文最想让你记住的:预检没过,那条 PUT 不会发生。如果你的服务端访问日志里只有 OPTIONS、没有 PUT,那不是「上传被拒绝」,是「上传根本没开始」。
一、把一次上传拆成两条请求
浏览器的顺序是固定的:
- 你的代码调用
fetch(url, { method: "PUT", headers: { Authorization: ..., "Content-Type": "application/json" }, body })。 - 浏览器判断这条请求「不够简单」,先发
OPTIONS,带上三个头:Origin(谁在请求)、Access-Control-Request-Method: PUT(我等下要用什么方法)、Access-Control-Request-Headers: authorization,content-type(我等下要带什么头)。 - 服务端回一条预检响应。Fetch 规范对这一条的要求比普通响应严格:「A successful HTTP response to a CORS-preflight request is similar, except it is restricted to an ok status, e.g., 200 or 204.」——预检必须是 2xx 里的 ok 状态,别指望用 3xx 或 403 加个头糊过去。
- 浏览器比对:
Access-Control-Allow-Origin是否覆盖当前来源、Access-Control-Allow-Methods是否含PUT、Access-Control-Allow-Headers是否覆盖authorization与content-type。三项都过,才发第 5 步。 - 真正的
PUT发出。这条的响应可以是任意状态码——规范原文:「A successful HTTP response … to a CORS request can use any status, as long as it includes the headers stated above with values matching up with the request.」——但必须带Access-Control-Allow-Origin,否则 JS 读到的是一个不透明的失败。 - 如果你的代码要读
ETag或x-amz-request-id,还需要Access-Control-Expose-Headers把它们列出来,否则响应头在 JS 里是读不到的(见第五节)。
注意第 4 步的判定粒度:预检的答案是按「来源 + URL + 方法 + 头名 + 是否带凭据」分别缓存的(Fetch 的 CORS-preflight cache 条目就由这几项组成)。这意味着「预检过了」只是「这一个方法、这一组头、这一个来源」过了;你换一个头、换一个子域、换一个端口,都会重新走一遍。
二、什么时候才会发预检
这一点决定你「该不该看到 OPTIONS」。Fetch 的主流程里,预检的触发条件是两条之一(原文):
- 「request’s method is not a CORS-safelisted method」——方法不是安全列表方法;安全列表方法只有三个:
GET、HEAD、POST。 - 「There is at least one item in the CORS-unsafe request-header names … for which there is no header-name cache entry match」——请求头里至少有一个「不安全」的头,且缓存里没有它的记录。
「不安全」的判定里有两个容易忽略的细节:
Content-Type只在三种值下算安全:application/x-www-form-urlencoded、multipart/form-data、text/plain(规范比对的是 MIME 的 essence)。application/json不在其中——所以POST+ JSON 一样会预检。- 头值太长也会掉出安全列表:规范在判断单个头是否安全时,第一步就是「If value’s length is greater than 128, then return false」。一条本该安全的头,值写长了就不安全了。
据此,把四种常见写法排一下(这一节的结论是按上面的规范条文推的,标注为编辑判断):
| 上传写法 | 会不会先发预检 | 原因 |
|---|---|---|
GET 取图,无自定义头 |
不会 | GET 是安全列表方法,头也都在安全列表里 |
POST + <form> 表单(multipart/form-data) |
通常不会 | 方法与 Content-Type 都安全;除非你加了别的头 |
POST + application/json |
会 | application/json 不在三种安全值里 |
PUT(无论什么 Content-Type) |
会 | PUT 不是安全列表方法 |
任意方法 + Authorization |
会 | Authorization 不是安全列表头(而且它是「非通配头」,见 5.3) |
三、S3 怎么评估:第一条匹配的规则
S3 的评估方式,官方两处写得一模一样:「When Amazon S3 receives a preflight request from a browser, it evaluates the CORS configuration for the bucket and uses the first CORSRule rule that matches the incoming browser request to enable a cross-origin request.」
一条规则要「匹配」,三个条件同时成立:
| 条件 | 请求里的什么 | 配置里的什么 |
|---|---|---|
| 来源 | Origin |
AllowedOrigins |
| 方法 | Access-Control-Request-Method |
AllowedMethods |
| 头 | Access-Control-Request-Headers |
AllowedHeaders |
「第一条匹配」这四个字有操作含义:规则顺序参与判定。如果第一条规则的 AllowedMethods 只有 GET,而你的请求是 PUT,这条不匹配,S3 继续看下一条;一旦某条匹配上,就用它回答,后面的规则不再参与。所以把「窄」的规则写在前面、「宽」的写在后面,是安全的;反过来,一条宽规则放在最前面,会让后面所有更细的规则永远轮不到(这一句是编辑判断,规范只说「第一条匹配」,没说顺序应该怎么排)。
几条边界,都来自配置页原文:
- 一个桶最多 100 条规则:「You can add up to 100 rules to the configuration.」
- 控制台只收 JSON:「If you are configuring CORS in the S3 console, you must use JSON to create a CORS configuration. The new S3 console only supports JSON CORS configurations.」
AllowedMethods只有五个取值:GET、PUT、POST、DELETE、HEAD。没有PATCH,也没有OPTIONS——预检本身不需要你把它写进方法列表。AllowedOrigins每个字符串最多一个*(例如http://*.example.com),也可以整体写*。AllowedHeaders每个字符串最多一个*(例如x-amz-*)。
还有一句必须单独拎出来,因为它决定了「CORS 通过」不等于「能上传」:「The ACLs and policies continue to apply when you enable CORS on your bucket.」 CORS 是浏览器侧的一道门,桶策略与 ACL 是服务端侧的另一道门。两道门都过,文件才落地。
四、两个 403,指向两处
S3 的 CORS 故障有两个不同的 403,响应体里的文本不一样,处理方向也不一样。把这两句当成两个错误码来记:
| 响应文本 | 含义 | 先查哪里 |
|---|---|---|
CORS is not enabled for this bucket |
桶上根本没有 CORS 配置 | 配置是否存在、是否配在了这个桶上 |
This CORS request is not allowed |
有配置,但没有一条规则匹配这条请求 | 三个条件里哪一个不成立 |
第二种 403 的官方解释是「a CORS rule in your CORS configuration doesn’t match the data in your request」,并明确列出三种可能:
- 「Origin is not allowed.」
- 「Methods are not allowed.」
- 「Requested headers are not allowed.」
也就是说,同一个 403、同一句文本,背后可能是三个完全不同的修法。不要看到「403 且提到 CORS」就去改 AllowedOrigins——先确定是三项里的哪一项。
本文只处理带这两句 CORS Response: 文本的 403。S3 还有别的 403(例如权限不足),不在这两页的讨论范围内,也不该用本文的结论去套(这一句是编辑判断)。
五、五个最常见的「不匹配」
5.1 来源:协议、主机、端口是一个整体
Origin 的匹配是字符串级的三段式:https://app.example.com 与下面这些都不相等:
http://app.example.com(协议不同)https://www.app.example.com(主机不同)https://app.example.com:8443(端口不同)http://localhost:4321与http://localhost:3000(端口不同,本地开发最常踩)
修法只有两种:把确切的来源写进 AllowedOrigins 列表,或者用 *(但见 5.4 关于带凭据的限制)。子域通配只能写成 https://*.example.com 这种形式——一个字符串里只能有一个 *,https://*.*.example.com 不是合法写法。
一处原文笔误,值得记下来:排错页在「Origin is not allowed」小节写的是「A wildcard character (*) in the AllowedOrigins element would match all HTTP methods」——AllowedOrigins 管的是来源,不是方法,这句里的 “methods” 与上下文不符;紧接着的「Methods are not allowed」小节才写「A wildcard character (*) in AllowedMethods would match all HTTP methods」。按元素语义,前者应为「匹配所有来源」。引用时以元素本身为准,不要按这句字面去推断「* 会放开方法」。
5.2 方法:PUT 没写就是 403
排错页的例子很直白:AllowedMethods 只写了 GET,那么「If an HTTP method (for example, PUT) was used in a CORS request or included in a pre-flight CORS request to your bucket but the method isn’t present in your CORS configuration, the request would result in a 403 Forbidden error.」
注意它把两种情况并列了:预检里的 Access-Control-Request-Method 不在列表,和实际请求的方法不在列表,都会 403。这也解释了为什么「预检 200」之后仍可能失败——预检过的是「方法 + 头」,不是「权限」。
5.3 头:Authorization 必须显式写,别赌 *
排错页给的例子是 Authorization 与 Content-MD5:只写了 Authorization,那么带 Content-MD5 的请求就 403;两个都要,就得两个都列。「If you want to pass both Authorization and Content-MD5 headers in a CORS request to your bucket, confirm that both headers are included in the AllowedHeaders element.」
这里有一个文档与规范之间的真冲突,值得单独说:
- Fetch 规范把
Authorization定义为 CORS non-wildcard request-header name(原文:「A CORS non-wildcard request-header name is a header name that is a byte-case-insensitive match forAuthorization.」)。预检校验时,如果请求里有这个头而Access-Control-Allow-Headers里没有逐字列出它,直接 network error——*不算数。 - 而 S3 的示例配置里出现过
"AllowedHeaders": ["*"],测试页的 curl 示例又同时带Access-Control-Request-Headers: Authorization。照字面拼在一起,会让人以为*能覆盖Authorization。
按规范,* 覆盖不了它。所以实操建议是(编辑判断):把 Authorization 逐字写进 AllowedHeaders,需要 Content-Type、Content-MD5、x-amz-* 就一并列出;* 只当兜底,不当唯一手段。这样即使浏览器按更严格的规范实现,你的配置也不会挂。
5.4 暴露头:请求成功,但 JS 读不到 ETag
这是最容易被误判成「上传失败」的一种:上传其实成功了,只是你的代码读不到回执。
排错页的原话:「The ExposeHeaders element in your CORS configuration identifies which response headers that you would like to make accessible to scripts and applications running in browsers」;并且「by default, browsers block access to custom headers for security reasons」。它给的例子很具体:配置里只列了 x-amz-meta-custom-header1,那么即使请求里要了 x-amz-meta-custom-header2,响应仍然返回 200 OK,但只有被允许的那个头会出现。
所以:要读 ETag 做完整性校验、要读 x-amz-request-id 排障、要读 x-amz-version-id 记录版本,就得把它们写进 ExposeHeaders。不写,response.headers.get("etag") 拿到的是 null——这不是上传失败,是暴露头没配。
另外一条来自测试页的注意:「When sending a preflight request, if any of the CORS request headers are not allowed, none of the response CORS headers are returned.」——一个头不过,整组 CORS 头都不回。所以你看到的往往不是「缺一个头」,而是「一个都没有」,这会让人误以为「CORS 完全没配」。
5.5 带凭据时,* 不算匹配
如果你用 cookie 或 credentials: "include",Fetch 的 CORS check 是这样的:Access-Control-Allow-Origin 为 * 时,只有在「credentials mode is not include」的情况下才算成功;带凭据时,服务端必须回与你来源逐字相同的那个值,并带 Access-Control-Allow-Credentials: true。S3 的测试页示例响应里就同时出现了 Access-Control-Allow-Origin: http://www.example1.com 与 Access-Control-Allow-Credentials: true。
一句话:带凭据的上传,AllowedOrigins: ["*"] 帮不了你。
六、直连能传、换域名就失败:看代理层
如果 curl 直连桶域名能过、走自己的域名(CDN、反代、WAF)就 403,问题大概率不在桶的 CORS 配置。S3 排错页专门有一节讲代理,给了三条要求:
- 「Configure the settings to allow the
OPTIONSmethod for HTTP requests.」——很多反代默认只放行GET/POST,OPTIONS直接被挡。 - 「Configure the proxy to forward the following headers:
Origin,Access-Control-Request-Headers, andAccess-Control-Request-Method.」——三个头少一个,预检就答不对。 - 「Configure the proxy settings to include the origin header in its cache key.」——原文解释了后果:「caching proxies that don’t include the origin header in their cache key may serve cached responses that don’t include the appropriate CORS headers for different origins」。也就是 A 域拿到的预检响应被缓存,B 域来的时候拿到了 A 的答案。
它举的例子是 CloudFront 的托管源请求策略,列出的设置是:转发 Origin、Access-Control-Request-Headers、Access-Control-Request-Method 三个头,不带 cookie,不带查询串。S3 测试页的示例预检响应里也带了 Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method——这条 Vary 正是给中间缓存看的:同一个 URL,来源不同,答案不同,别混着缓存。
七、十分钟自测:把预检单独拿出来跑
不用开浏览器,一条 curl 就能看到服务端到底怎么答。这是 S3 测试页给的命令(原样,替换成你自己的桶与对象前缀):
curl -v -X OPTIONS \
-H "Origin: http://www.example1.com" \
-H "Access-Control-Request-Method: PUT" \
-H "Access-Control-Request-Headers: Authorization" \
-H "Access-Control-Expose-Headers: x-amz-meta-custom-header" \
"http://bucket_name.s3.amazonaws.com/object_prefix_name"
成功的响应该长这样(测试页的示例,节选):
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: http://www.example1.com
< Access-Control-Allow-Methods: GET, PUT, POST, DELETE
< Access-Control-Allow-Headers: Authorization
< Access-Control-Expose-Headers: x-amz-meta-custom-header
< Access-Control-Allow-Credentials: true
< Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
判读按这张表走:
| 你看到什么 | 说明 | 下一步 |
|---|---|---|
200 + Allow-Origin 与你的来源一致 + Allow-Methods 含 PUT + Allow-Headers 含 authorization |
预检这一关过了 | 失败在别处:查桶策略/ACL、查代理有没有改请求 |
200 但没有 Access-Control-* 头 |
请求没被当成 CORS 请求(常见于 Origin 没带对,或被代理去掉了) |
先确认代理转发了 Origin |
403 + CORS is not enabled for this bucket |
桶上没有 CORS 配置 | 配 PutBucketCors,确认配的是这个桶 |
403 + This CORS request is not allowed |
有配置,三项里有一项不匹配 | 用第二节的三条件逐个对:来源、方法、头 |
403 但响应体里没有 CORS Response: 文本 |
不是这两类 CORS 错误 | 按权限/签名问题查,别改 CORS |
跑的时候有个顺序讲究(编辑判断):先直连桶的域名跑一次,再换成你自己的域名/CDN 域名跑一次。两次结果不一样,问题就在代理层,改桶配置是白改。
还要提醒一句:curl 只是把服务端的回答原样打出来,它不执行浏览器的 CORS 策略——所以 curl 里 PUT 成功,不代表浏览器里也成功;反过来 curl 的预检 403,浏览器一定过不去。前者是「服务端允许」,后者才是「浏览器会拦」。
八、十二项上线前核对
- 这次上传用的是哪个方法? 不是
GET/HEAD/POST就一定会预检,别按「读能成」推断「写也能成」。 Content-Type是什么?application/json会触发预检;multipart/form-data通常不会。AllowedOrigins里写的是完整来源吗? 协议、主机、端口三段都要对;本地开发的端口变了要一起改。AllowedMethods里有PUT吗? 只写GET是最常见的漏项。Authorization逐字写进AllowedHeaders了吗? 别只写*。Content-Type/Content-MD5/自定义x-amz-*头都列了吗? 漏一个,整组 CORS 头都不回。- 要读的响应头进
ExposeHeaders了吗?ETag、x-amz-request-id、x-amz-version-id默认读不到。 - 规则顺序对吗? 窄规则在前,宽规则在后;别让一条
*挡在所有细分规则前面。 - 桶策略与 ACL 也放行了吗? CORS 过了不等于有权限,「The ACLs and policies continue to apply」。
- 带凭据吗? 带就必须回具体来源 +
Access-Control-Allow-Credentials: true,*不行。 - 代理放行
OPTIONS、转发三个头了吗? 缓存键里有没有Origin,或者响应有没有Vary。 - 改完配置复测了吗? 预检结果会被缓存(见下),改完仍失败不一定是没生效。
关于第 12 条,把缓存的边界说清楚,避免两个方向的误判:
- S3 的
MaxAgeSeconds是告诉浏览器可以缓存多久;配置页的示例值是3000,那是示例,不是默认值。 - 浏览器那边:预检响应里没有
Access-Control-Max-Age时,Fetch 规定max-age取 5 秒(原文:「If max-age is failure or null, then set max-age to 5.」);规范同时允许 UA 设一个「imposed limit」把更长的值压下来,也允许「Cache entries may be removed before that moment arrives」。所以具体缓存多久由浏览器决定,不由你决定——不要把它当成「配了就一定 N 分钟内生效」的保证。 - 实操上:改完配置先用一个新的来源(换端口、换 profile、无痕窗口)复测,能排除掉旧缓存的干扰(编辑判断)。
九、六种常见误读
误读一:「配了 CORS 就能上传。」
不能。CORS 只决定浏览器允不允许把响应交给你的 JS;桶策略与 ACL 照常生效(原文那句 “continue to apply”)。预检 200、PUT 仍然 403 的情况,问题在权限不在 CORS。
误读二:「403 就是 CORS 没配。」
两种 CORS 403 的文本不一样,修法也不一样:CORS is not enabled for this bucket 是没配,This CORS request is not allowed 是配了没匹配。而且 S3 还有非 CORS 的 403。看文本,不要看状态码。
误读三:「* 可以随便用。」
AllowedOrigins 与 AllowedHeaders 的每个字符串最多一个 *;Authorization 是规范里的非通配头,* 覆盖不了;带凭据时 Allow-Origin: * 也不算匹配。三处都会让「看起来配了」变成「实际没过」。
误读四:「预检过了,就说明上传会成功。」 预检只回答「这个方法 + 这组头 + 这个来源」行不行。权限、签名过期、对象锁、桶策略、代理改写,都在这之后。
误读五:「GET 能取到图,说明 CORS 配好了。」
GET 往往根本不预检。你验证的是「简单请求能不能读」,不是「复杂请求能不能写」。
误读六:「上传失败是服务端拒绝。」
看日志:只有 OPTIONS 没有 PUT,说明浏览器在第 4 步就停了,请求从未到达。这时改服务端权限、看重试逻辑都没有意义,要改的是预检的答案。
十、本周就能做的五件事
- 打开浏览器开发者工具的 Network 面板,重跑一次失败的上传。 看有没有
OPTIONS那一条、它的状态码是多少、响应里有没有Access-Control-Allow-*。这一步能把「预检失败」和「上传被拒」分开,后面所有排查都建立在这个判断上。 - 把
curl -X OPTIONS那条命令存成脚本。 参数换成你自己的来源、方法与头;直连域名与自定义域名各跑一次。以后每次改 CORS 配置,先跑它。 - 对着 5.1–5.5 五项,把你的
AllowedOrigins/AllowedMethods/AllowedHeaders/ExposeHeaders逐行读一遍。 多数问题在这一步就能看出来:端口、PUT、Authorization、ETag,四个高频漏项。 - 把「要读哪些响应头」写进前端代码的注释。
ETag要读,就得在桶的ExposeHeaders里有;这个依赖关系不写下来,下一个人加功能时还会再踩一次。 - 如果你的上传走 CDN 或反代,把
OPTIONS与三个转发头写进基础设施配置的检查清单。 这类故障的特点是「直连一直好、上线才坏」,靠人记不住。
如果只能记住一件事:这条链路上有两条请求,失败的那条往往不是你写的那条。
边界说明:本文关于 S3 的陈述全部来自 Amazon S3 用户指南的四个公开页面,关于浏览器判定与预检缓存的陈述来自 WHATWG Fetch Standard(Living Standard,无固定版本号,引用为 2026-10-30 抓取时的文本),五个来源均于当日逐条访问并验证为 HTTP 200(字节数见「来源」)。不写任何浏览器品牌与版本的具体行为(这几份来源都没有按浏览器列差异);不把 MaxAgeSeconds 的示例值 3000 说成默认值,也不承诺「N 分钟内一定生效」(规范只给 5 秒兜底与「UA 可设上限、可提前清除」,没有给统一上限);不写「配了 CORS 就能上传」(原文明确 ACL 与桶策略照常生效);不把两种 403 混为一谈,也不写「403 一定是 CORS 问题」;不写「* 可以覆盖任何头」(Authorization 是规范里的非通配头);不写「GET 能取到就说明 CORS 配好了」。文中标注为编辑判断的有:第二节五种写法的预检结论表、第三节「窄规则在前」的排序建议、第五节对 AllowedHeaders: ["*"] 与 Authorization 冲突的处置建议、第七节的排查顺序与「curl 不执行浏览器策略」的说明、第 12 条用新来源复测的做法、以及第十节的五件事。本文只覆盖 S3 通用存储桶的 CORS 语义与浏览器侧预检,不涉及签名 URL 的生成、S3 Object Lambda 的 CORS 行为(原文另有一节),也不涉及非 S3 存储服务的实现差异。
来源
以下页面均于 2026-10-30 逐条访问并验证为 HTTP 200;字节数为当日抓取的实际大小。
- Using cross-origin resource sharing (CORS)核查于 2026/10/30(200,15 873 B):CORS 的定义(「defines a way for client web applications that are loaded in one domain to interact with resources in a different domain」);预检评估「uses the first CORSRule rule that matches the incoming browser request」;规则匹配的三个条件(
Origin↔AllowedOrigins、Access-Control-Request-Method↔AllowedMethods、Access-Control-Request-Headers↔AllowedHeaders);「The ACLs and policies continue to apply when you enable CORS on your bucket.」;场景 1(网页对同桶发GET/PUT)与场景 2(web font 需要预检,「browsers require a CORS check (also called a preflight check) for loading web fonts」)。 - Troubleshooting CORS核查于 2026/10/30(200,28 397 B):两种 403——
CORS is not enabled for this bucket(未配置 CORS)与This CORS request is not allowed(有配置但无规则匹配),后者三个原因「Origin is not allowed.」「Methods are not allowed.」「Requested headers are not allowed.」;「For each request that Amazon S3 receives, you must have a CORS rule in your CORS configuration that matches the data in your request.」;AllowedMethods只接受GET PUT POST DELETE HEAD,未列出的方法(如PUT)会 403;AllowedHeaders需同时列出Authorization与Content-MD5;ExposeHeaders决定脚本能读哪些响应头,「by default, browsers block access to custom headers for security reasons」,未列出的头「the response would still return a 200 OK」但只返回被允许的头;代理三条要求(放行OPTIONS、转发三个头、缓存键含Origin)与 CloudFront 托管策略的具体设置;一处原文笔误(AllowedOrigins小节写「would match all HTTP methods」)。 - Elements of a CORS configuration核查于 2026/10/30(200,23 883 B):一个桶最多 100 条规则;控制台只支持 JSON;
AllowedMethods五个取值;AllowedOrigins每个字符串只能有一个*(如http://*.example.com),也可整体*,可只允许https;AllowedHeaders每个字符串最多一个*(如x-amz-*),「Amazon S3 will send only the allowed headers in a response that were requested」;ExposeHeaders让 JS 能读响应头;MaxAgeSeconds是浏览器缓存预检响应的秒数(按资源、方法、来源区分),示例配置为3000。 - Testing CORS核查于 2026/10/30(200,17 874 B):
curl -v -X OPTIONS自测命令及参数说明;示例 200 响应头(Access-Control-Allow-Origin/-Allow-Methods/-Allow-Headers/Access-Control-Expose-Headers/-Allow-Credentials: true/Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method);「if any of the CORS request headers are not allowed, none of the response CORS headers are returned」;Access-Control-Request-Headers与Access-Control-Expose-Headers为可选。 - Fetch Standard核查于 2026/10/30(200,1 854 393 B,Live Standard):CORS 安全列表方法只有
GET/HEAD/POST;预检触发条件(方法不是安全列表方法,或存在无缓存命中的 CORS-unsafe 请求头);Content-Type仅在application/x-www-form-urlencoded/multipart/form-data/text/plain三种 essence 下安全;单头值长度大于 128 即不算安全头;Authorization为「CORS non-wildcard request-header name」,*不覆盖;预检响应的Access-Control-Max-Age缺省取 5 秒、可被 UA 的上限压低、缓存条目可提前移除;预检缓存条目由 network partition key、来源、URL、max-age、credentials、method、header name 组成;带 credentials 时Allow-Origin: *不算成功,需回具体来源并带Access-Control-Allow-Credentials: true;预检必须为 ok 状态(如 200 或 204),普通 CORS 响应可用任意状态码;预检为 network error 时不再发实际请求。