入门指南

取图能显示、上传却 403:预检是另一条请求,两个 403 指向两处

浏览器直传失败,往往不是权限:预检是另发的 OPTIONS,S3 用第一条匹配的规则回答,两个 403 分别指向「没配」与「没匹配」。把方法、头、暴露头与代理层拆开核对。

预检没过,真正那条 PUT 根本不会发出去。

作者
AOPC 编辑部
发布
AOPC 编辑部
发布时间
更新
2026/10/30
核查
已复核
一个人在白板前把一次浏览器上传拆成两条请求:先发的 OPTIONS 预检,与被它放行之后才发出的 PUT
经营者责任与 AI 执行层的关系

先看答案

适合谁
  • 把大文件从浏览器直传对象存储,控制台只报一句 CORS 错误的人
  • 桶上「明明配了 CORS」,却分不清是没配好还是根本没匹配上的人
  • 直连存储能传、换成自己的域名或 CDN 就失败的人
你会获得
上传失败先分清:是预检没答,还是答了没放行。
发布时间
实质更新
发布
AOPC 编辑部

60 秒结论

一分钟看懂这篇文章

预检没过,真正那条 PUT 根本不会发出去。

人负责

  • 判断失败发生在预检还是上传
  • 决定 CORS 规则怎么写、按什么顺序写

AI 负责

  • 用 curl 复现预检并读响应头
  • 核对方法、头与暴露头三组清单
  1. 分请求
  2. 查规则
  3. 读 403
  4. 测代理

直接答案

「图片能显示,上传同一个桶却 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,那不是「上传被拒绝」,是「上传根本没开始」。

一、把一次上传拆成两条请求

浏览器的顺序是固定的:

  1. 你的代码调用 fetch(url, { method: "PUT", headers: { Authorization: ..., "Content-Type": "application/json" }, body })。
  2. 浏览器判断这条请求「不够简单」,先发 OPTIONS,带上三个头:Origin(谁在请求)、Access-Control-Request-Method: PUT(我等下要用什么方法)、Access-Control-Request-Headers: authorization,content-type(我等下要带什么头)。
  3. 服务端回一条预检响应。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 加个头糊过去。
  4. 浏览器比对:Access-Control-Allow-Origin 是否覆盖当前来源、Access-Control-Allow-Methods 是否含 PUT、Access-Control-Allow-Headers 是否覆盖 authorization 与 content-type。三项都过,才发第 5 步。
  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 读到的是一个不透明的失败。
  6. 如果你的代码要读 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 for Authorization.」)。预检校验时,如果请求里有这个头而 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 排错页专门有一节讲代理,给了三条要求:

  1. 「Configure the settings to allow the OPTIONS method for HTTP requests.」——很多反代默认只放行 GET/POST,OPTIONS 直接被挡。
  2. 「Configure the proxy to forward the following headers: Origin, Access-Control-Request-Headers, and Access-Control-Request-Method.」——三个头少一个,预检就答不对。
  3. 「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,浏览器一定过不去。前者是「服务端允许」,后者才是「浏览器会拦」。

八、十二项上线前核对

  1. 这次上传用的是哪个方法? 不是 GET/HEAD/POST 就一定会预检,别按「读能成」推断「写也能成」。
  2. Content-Type 是什么? application/json 会触发预检;multipart/form-data 通常不会。
  3. AllowedOrigins 里写的是完整来源吗? 协议、主机、端口三段都要对;本地开发的端口变了要一起改。
  4. AllowedMethods 里有 PUT 吗? 只写 GET 是最常见的漏项。
  5. Authorization 逐字写进 AllowedHeaders 了吗? 别只写 *。
  6. Content-Type/Content-MD5/自定义 x-amz-* 头都列了吗? 漏一个,整组 CORS 头都不回。
  7. 要读的响应头进 ExposeHeaders 了吗? ETag、x-amz-request-id、x-amz-version-id 默认读不到。
  8. 规则顺序对吗? 窄规则在前,宽规则在后;别让一条 * 挡在所有细分规则前面。
  9. 桶策略与 ACL 也放行了吗? CORS 过了不等于有权限,「The ACLs and policies continue to apply」。
  10. 带凭据吗? 带就必须回具体来源 + Access-Control-Allow-Credentials: true,* 不行。
  11. 代理放行 OPTIONS、转发三个头了吗? 缓存键里有没有 Origin,或者响应有没有 Vary。
  12. 改完配置复测了吗? 预检结果会被缓存(见下),改完仍失败不一定是没生效。

关于第 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 步就停了,请求从未到达。这时改服务端权限、看重试逻辑都没有意义,要改的是预检的答案。

十、本周就能做的五件事

  1. 打开浏览器开发者工具的 Network 面板,重跑一次失败的上传。 看有没有 OPTIONS 那一条、它的状态码是多少、响应里有没有 Access-Control-Allow-*。这一步能把「预检失败」和「上传被拒」分开,后面所有排查都建立在这个判断上。
  2. 把 curl -X OPTIONS 那条命令存成脚本。 参数换成你自己的来源、方法与头;直连域名与自定义域名各跑一次。以后每次改 CORS 配置,先跑它。
  3. 对着 5.1–5.5 五项,把你的 AllowedOrigins/AllowedMethods/AllowedHeaders/ExposeHeaders 逐行读一遍。 多数问题在这一步就能看出来:端口、PUT、Authorization、ETag,四个高频漏项。
  4. 把「要读哪些响应头」写进前端代码的注释。 ETag 要读,就得在桶的 ExposeHeaders 里有;这个依赖关系不写下来,下一个人加功能时还会再踩一次。
  5. 如果你的上传走 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;字节数为当日抓取的实际大小。

  1. 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」)。
  2. 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」)。
  3. 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。
  4. 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 为可选。
  5. 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 时不再发实际请求。

唯一主行动

把下一步变成可执行动作

进入发布、维护与回滚工作流