想象你住在“青柠社区”,这是个封闭式小区——保安只放行本社区的住户,外来人员一律拦在门外。这就是浏览器的同源策略:只有协议、域名、端口完全一致的页面,才能互相读取资源。可如今互联网应用大多是分布式部署的:前端在 A 社区,后端 API 在 B 社区。你总不能每次都翻墙过去吧? 于是,浏览器社区和服务器社区之间约定了一套“跨社区拜访制度”——CORS(跨域资源共享)。拜访有两种方式:一种是“临时拜访卡”,简单登记就放行(简单请求);另一种是“先到物业办公室开拜访单”,保安核实后才放行(预检请求)。物业办公室会在拜访单上写清楚“允许谁进入”(Access-Control-Allow-Origin)、“是否允许携带贵重物品”(Access-Control-Allow-Credentials)、“拜访单有效期多久”(Access-Control-Max-Age)。 这篇文章就像一本“跨社区拜访手册”,带你读懂浏览器、前端、后端三方之间的每一张单据、每一个盖章,从此跨域配置不再靠复制粘贴碰运气。
对 AI 从业者而言,理解 CORS 意味着能独立排查前后端联调中 80% 的跨域报错,这是从“调通接口”走向“懂网络协议”的必经一步,也是大模型应用开发中部署 Web 服务的基本功。
可视化演示
\u5c55\u793a\u5341\u8fdb\u5236\u3001\u4e8c\u8fdb\u5236\u548c\u5341\u516d\u8fdb\u5236\u4e4b\u95f4\u7684\u8f6c\u6362\u8fc7\u7a0b\uff0c\u901a\u8fc7\u4f4d\u6743\u5206\u89e3\u548c\u5341\u516d\u8fdb\u5236\u7f29\u5199\u7406\u89e3\u6570\u5b57\u8868\u793a\u3002
本节配有交互动效(进入视口后自动加载,约 3KB)。
\u8fdb\u5236\u8f6c\u6362 \u4e8c\u8fdb\u5236\u4f4d \u5f53\u524d\u4f4d \u2211 bit\u00d72^n \u4f4d\u6743\u5206\u89e3\u8f6c\u6362\u8fc7\u7a0b
同源策略
青柠社区门口的保安是出了名的严格。他的登记本上写着三条规矩:协议要一样(访客和住户都得走同样的门禁协议),域名要一样(都得是青柠社区的门牌),端口要一样(都得走同一个侧门)。只有三条都满足,保安才认为你们是“一家人”,资源随便看。这就是浏览器的同源策略。
在 Web 世界里,“同源”指协议、域名、端口三者完全一致。比如 https://api.example.com 和 https://www.example.com 虽然域名都带有 example.com,但二级域名不同,就是跨域。同源策略是浏览器最基础的安全防线——它保证一个页面只能读取同源资源,防止恶意网站偷看你在银行页面的数据。
同源策略的拦截对象主要是“读取”操作。页面可以发送请求(比如提交表单),但浏览器会拦截脚本对响应数据的读取。特别是 XMLHttpRequest 和 fetch 这类 API,跨域请求发出去了,服务器也响应了,但浏览器把响应数据扣在保安亭,JavaScript 拿不到。
保安的规矩虽然严谨,却也挡了不少正经客人。繁花社区的前端要调用青柠社区的后端接口,数据就是拿不到。好在社区之间早已有了正式的拜访制度——CORS。它不是让保安放水,而是让跨社区拜访变得有章可循、签过协议再放行。
- 协议比较比较两个 URL 的协议部分(http/https)是否完全相同,这是第一道关卡。
- 域名比较比较两个 URL 的主机名是否完全一致,
example.com和www.example.com被视为不同。 - 端口比较比较端口号是否相同,没有显式写端口时默认 http 为 80、https 为 443。
- 逻辑与三个条件必须同时成立才算同源,任何一个不满足都触发 CORS 检查流程。
简单请求
社区之间商量出了两种拜访方式。第一种叫“临时拜访卡”:访客在门口登记一下姓名和事由,保安直接放行,不需要去物业办公室开复杂的单子。对应到 HTTP 世界里,这类请求叫简单请求。只要满足三个条件,浏览器就不会触发预检流程,直接发出请求。
第一个条件是方法限定:只能是 GET、POST、HEAD 三种。这三种方法只读或只提交数据,不涉及修改资源的危险操作。第二个条件是 Content-Type 限定:只能是 text/plain、multipart/form-data、application/x-www-form-urlencoded 这三种。这三种编码格式都是表单页面的原生类型,不会携带复杂结构。第三个条件是请求头限定:只能使用 CORS 安全列表内的请求头(如 Accept、Content-Type 等),不能夹带自定义请求头。
如果三个条件全部满足,浏览器就把请求当作“临时拜访卡”,直接发出并允许读取响应。
临时拜访卡的好处是快,但限制也明显:如果你想用 PUT 方法或者带一个 X-Auth-Token 的自定义头,保安就没有权力放行了。这时候你有两个选择:放弃这些需求,或者走第二条路——预检请求。怎么选,取决于你的业务到底需要多复杂的操作。
Access-Control-Allow-Origin 就能被页面读取。- 方法集合请求方法必须是 GET、POST、HEAD 三者之一,这是第一道关卡。
- 类型集合请求的 Content-Type 必须是三种表单编码格式之一,用 \(\mathcal{C}_{safe}\) 表示安全类型集合。
- 无自定义头请求不能携带自定义请求头,所有头部必须来自浏览器自动添加的安全头列表。
- 三个 AND三个条件全部满足时,请求走免预检的绿色通道。
预检请求
假设你带着一个需要特殊审批的诉求来拜访——比如用 PUT 方法修改资源,或者带一个自定义的 X-User-Token 头。保安不敢做主,他让你先去物业办公室开一张“拜访单”。这个去物业办公室咨询的过程,在 HTTP 里就是预检请求。
预检请求用的是 OPTIONS 方法,它在正式请求之前发出,专门询问三件事:允许哪些方法?(Access-Control-Request-Method)、允许哪些请求头?(Access-Control-Request-Headers)、我来自哪个源?(Origin)。服务器看到这张咨询单后,会核实自己的授权策略,然后返回预检响应。
如果预检响应表明“允许 PUT 方法、允许 X-User-Token 头”,浏览器才敢发出真正的请求。如果服务器说“不允许”,浏览器就直接拦下,连正式请求都不会发出——你连犯错的机会都没有,却能省下一次无效的网络往返。
物业办公室的效率比你想象的高——它不只答复当下的问题,还会顺手给你一张“长期拜访单”。只要服务器在预检响应里写了 Access-Control-Max-Age,这张单子在一定时间内都有效。拜访单开好了,但能不能进门,还得看保安手里的那份白名单——也就是服务器配置的那一串响应头。
- OPTIONS 方法预检请求固定使用 OPTIONS 方法,它不代表任何业务操作,只用于“询问”。
- 请求方法声明浏览器告诉服务器“我接下来想用 PUT 方法”,对应
Access-Control-Request-Method头。 - 请求头声明浏览器告诉服务器“我还想携带 X-User-Token 头”,对应
Access-Control-Request-Headers头。 - 来源声明浏览器在 Origin 头中声明自己是哪个源发起的请求,服务器据此判断是否信任。
响应头授权
物业办公室的工作台上放着一本“准入白名单”,上面用三种不同颜色的笔写满了规则。这张白名单才是整个跨社区拜访制度的核心——它的正式名称叫响应头授权。
第一种笔写的是来源名单(Access-Control-Allow-Origin):指定哪些源可以读取响应。它可以写死一个具体的源(如 https://front.example.com),也可以写个通配符 * 表示“谁都能来”。第二种笔写的是方法名单(Access-Control-Allow-Methods):列出允许的 HTTP 方法,如 GET, POST, PUT, DELETE。第三种笔写的是请求头名单(Access-Control-Allow-Headers):列出允许携带的自定义请求头,如 Content-Type, X-User-Token。
这三个响应头配合在一起,就是物业办公室给浏览器看的“准入白名单”。浏览器拿到响应后,会拿白名单去核对眼前这个请求:源在不在名单里?方法在不在名单里?头在不在名单里?全部命中才放行,有一条不满足就拦截。
白名单的配置是一个繁琐的体力活:每加一个头、每加一种方法,都要改动服务端配置。更麻烦的是,如果忘了配 Allow-Headers,浏览器会以"Request header field xxx is not allowed by Access-Control-Allow-Headers"为由拦下请求。这个报错是前端联调中最常见的 CORS 之一,原因就在这本白名单少写了一行。
- 来源匹配请求的 Origin 必须在
Access-Control-Allow-Origin声明的集合中,精确字符串匹配。 - 方法匹配请求的 Method 必须在
Access-Control-Allow-Methods声明的集合中。 - 头的子集关系请求携带的每个自定义头,都必须在
Access-Control-Allow-Headers的集合中,用子集符号表示。 - 三重与三个条件必须同时满足,响应数据才允许被脚本读取。
请求头受限
为什么浏览器对请求头这么敏感?回到社区的比喻:保安能快速放行的前提,是访客“只带常规物品”。如果你拎着一个看不清内容的黑色公文包,保安必须核实里面装的是什么。HTTP 中的自定义请求头就是这个黑色公文包——它可能携带认证令牌、追踪信息等敏感数据,浏览器不敢擅自放行。
因此 CORS 定义了一个安全请求头列表,只允许这些头出现在简单请求中。比如 Accept、Accept-Language、Content-Language、Content-Type(限三种值),以及 DPR、Downlink 等少数几个浏览器自动添加的头。除此之外的请求头,如 Authorization、X-Custom-Header,一律需要通过预检申报。
这种机制的本质是最小授权:简单请求只放行浏览器可以自己生成、内容可预测的安全头;凡是可能携带敏感信息的头,都要经过服务器显式授权才能使用,用来防止跨站请求伪造(CSRF)等攻击。
理解了这个机制,你就能解释一个前端经典困惑:“为什么我加了一个 Authorization 头,请求就多了一次 OPTIONS?”因为 Authorization 不在安全头列表里,浏览器需要先征求服务器同意才能让你带这个头。这就像保安看到你包里露出一角的工作证(Authorization),必须确认你确实是来办事的才放行。
- 安全头集合定义 CORS 安全请求头集合,包含 Accept、Accept-Language等浏览器标准头。
- Content-Type 限制Content-Type 只有三种取值时可以免预检,用 \(\text{|}_{3}\) 表示“限三种类型”。
- 浏览器自动头浏览器自动添加的头部也属于安全范围,用户无法控制它们的内容,所以可信。
- 其余皆需预检落在集合之外的请求头(如 Authorization、X-Token)都必须先发送预检请求申报。
凭证携带
在一些场景里,跨域请求需要携带凭证——最典型的就是浏览器自动附加的 Cookie。回到社区的比喻:你要去隔壁社区拜访,口袋里装着自家的门禁卡(Cookie),这张卡在对方社区不一定被承认。CORS 对凭证携带有一套专门的规则,比普通跨域更严格。
第一个规则是前端需要显式开启:XMLHttpRequest 要设置 withCredentials = true,fetch 要设置 credentials: 'include'。默认情况下浏览器不会在跨域请求中附带 Cookie,这是出于安全考虑——防止第三方网站借用你的登录态。第二个规则是服务端必须响应 Access-Control-Allow-Credentials: true:服务器明确说“我接受你的门禁卡”。
这里有个最容易踩的坑:当 Access-Control-Allow-Credentials: true 时,Access-Control-Allow-Origin 不能是 *,且 Allow-Headers 也不能是 *。因为通配符意味着“对所有人开放”,而携带凭证的请求必须知道具体是哪个源,否则凭证就可能被任意第三方读取。
理解了凭证携带的规则,你就掌握了调试“跨域登录”问题的钥匙。那种“明明在后端配了 CORS,但跨域请求就是不带 Cookie”的现象,往往是三缺一:要么前端没开 withCredentials,要么后端忘了配 Allow-Credentials,要么错误地用了 * 想图省事。
- 前端开启标志前端必须主动设置 withCredentials 为 true,否则即使服务器允许,Cookie 也不会携带。
- 服务器允许标志服务器必须响应
Access-Control-Allow-Credentials: true,表示接受凭证。 - 通配符禁用携带凭证时 Allow-Origin 不能使用
*,必须指定确切的源字符串。 - 三重条件只有三个条件全部满足,浏览器才会在跨域请求中附带 Cookie 并允许读取响应。
缓存与兜底
每次跨域请求都要走一遍预检流程,网络开销不小。好在你拿到了物业办公室发的“长期拜访单”——浏览器会在一定时间内缓存预检结果,这个时间由响应头 Access-Control-Max-Age 控制,单位是秒。比如 Access-Control-Max-Age: 600 表示 10 分钟内,同源同方法的预检请求不再重复发送。
缓存虽然是省事儿了,但生产环境的 CORS 配置依然要细心。一个常见的实践困境是:前端的源不固定(本地开发 localhost:8080、测试环境 test.example.com、生产环境 app.example.com),如果服务端只允许一个源,就需要动态读取请求的 Origin 头来回写。许多团队用 Nginx 统一处理 CORS,在反向代理层加上响应头,应用代码完全不感知跨域的存在。
还有一类容易踩坑的是兜底配置。比如你要允许所有源访问,但又要携带凭证,此时必须动态回显 Origin 的值,而不是用 *。再比如某些老版本浏览器不支持 Access-Control-Allow-Headers 多值,需要写成 Content-Type, Authorization 而不是 Content-Type, Authorization,。这些细节决定了线上真正跑起来时,跨域接口是丝滑还是翻车。
把缓存和兜底配置做好,CORS 就从“偶尔报错的麻烦事”变成了“静默运行的基础设施”。回顾整个拜访流程:同源策略是“非本社区不可入内”的规矩,简单请求是“登记即放行”的绿色通道,预检请求是“先问后做”的审批流程,响应头是物业的授权白名单,请求头限制是“禁止携带不明物品”的安全底线,凭证携带是“门禁卡互认”的特别条款——每一环都是浏览器和服务器之间的一种默契。
- 缓存时长定义服务器通过 Access-Control-Max-Age 头告诉浏览器预检结果可以缓存多久。
- 缓存命中在缓存有效期内,同一源对同一资源的预检请求不会再发送,直接复用上次的结果。
- 缓存失效超过 Max-Age 后,浏览器重新发送 OPTIONS 预检请求,确保授权策略仍是最新的。
AI 应用场景
场景:AI 模型服务的跨域调用
阶段:模型部署与集成
解决的问题:训练好的机器学习模型通常部署在独立的后端服务上(如 model.example.com:8000),而前端可视化平台跑在 app.example.com。传统方式下,前端直接 fetch 模型 API 会被同源策略拦截,AI 应用无法在网页端实时调用模型推理。
具体案例:某公司做了个 AI 图像识别平台,前端要调用模型服务的 /predict 接口。由于模型服务与前端不同源,浏览器拦下了所有响应。通过 CORS 配置,后端加上 Access-Control-Allow-Origin: https://app.example.com、Access-Control-Allow-Methods: POST, OPTIONS,并在前端用 fetch('/predict', { method: 'POST', headers: { 'Content-Type': 'application/json' } }) 请求,跨域推理畅通无阻。如果模型接口要用 Authorization 头携带 API Key,还需配置 Access-Control-Allow-Headers: Authorization 并处理后端的预检请求。
场景:大模型应用的流式响应与凭证
阶段:前端应用开发
解决的问题:基于 LLM 的聊天应用需要流式获取生成结果(text/event-stream),同时要携带用户登录凭证。如果前后端分离部署,CORS 凭证规则直接决定流式请求能否成功。
具体案例:一个 AI 客服系统前端在 chat.example.com,后端在 api.example.com,用户登录后用 Cookie 维持会话。配置 Access-Control-Allow-Origin: https://chat.example.com(不能用 \*)、Access-Control-Allow-Credentials: true,前端 fetch 设置 credentials: 'include',同时允许 Content-Type 头。这样流式聊天才能既跨域又带凭证,AI 回复逐字输出。
场景:AI 网关的统一跨域策略
阶段:平台架构设计
解决的问题:企业内部有多个 AI 模型服务、多个前端应用,如果每个服务各配各的 CORS,规则容易失控。通过统一的 API 网关(如 Nginx、Kong)统一配置 CORS,形成“单一授权入口”。
具体案例:某中台团队在 Nginx 层统一配置 add_header 'Access-Control-Allow-Origin' $http_origin always; 并配合 Access-Control-Allow-Credentials: true,对 OPTIONS 请求直接返回 204。后端模型服务无需关心跨域逻辑,前端统一从网关入口调用不同的 AI 能力,A/B 测试新模型时也不需要改任何跨域配置。
Python 演示
# -*- coding: utf-8 -*-"""CORS 预检请求与响应头处理演示用 Python 标准库实现一个支持 CORS 的简单 HTTP 服务,模拟前端跨域调用时的预检(OPTIONS)与真实请求(GET/POST)流程。"""import jsonfrom http.server import BaseHTTPRequestHandler, HTTPServer ALLOW_ORIGIN = "http://front.example.com" # 配置允许的源 class CORSHandler(BaseHTTPRequestHandler): # 统一为所有响应附加 CORS 头 def _set_cors_headers(self): self.send_header("Access-Control-Allow-Origin", ALLOW_ORIGIN) self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") self.send_header("Access-Control-Allow-Headers", "Content-Type, X-User-Token") self.send_header("Access-Control-Max-Age", "600") # 预检结果缓存600秒 # 预检请求:浏览器先发 OPTIONS 咨询,服务端返回授权声明 def do_OPTIONS(self): self.send_response(204) self._set_cors_headers() self.end_headers() # GET 请求:返回一个 JSON 响应 def do_GET(self): self.send_response(200) self._set_cors_headers() self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"message": "CORS ok"}).encode()) # POST 请求:接收 JSON 并回显 def do_POST(self): length = int(self.headers.get("Content-Length", 0)) body = json.loads(self.rfile.read(length) or b"{}") self.send_response(200) self._set_cors_headers() self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"received": body}).encode()) def log_message(self, fmt, *args): print(f"[CORS本地服务] {fmt % args}") if __name__ == "__main__": server = HTTPServer(("127.0.0.1", 8080), CORSHandler) print("启动 CORS 演示服务: http://127.0.0.1:8080") server.serve_forever()
ALLOW_ORIGIN = "http://front.example.com"定义允许的源,相当于物业白名单里的来源名单。def _set_cors_headers(self)统一附加 CORS 响应头,避免每个处理方法重复写。Access-Control-Allow-Methods声明允许的请求方法,预检时浏览器核对它。Access-Control-Allow-Headers声明允许的请求头,前端带X-User-Token时才不会拦截。Access-Control-Max-Age, "600"预检结果缓存 10 分钟,减少重复 OPTIONS 请求。def do_OPTIONS(self)处理预检请求,返回 204 并附 CORS 头,不执行业务逻辑。def do_GET(self)处理简单请求,GET配合无自定义头时可以免预检直接走这里。def do_POST(self)处理需要预检的 POST 请求,演示服务端回显 JSON 数据。
常见误区
误区初学者听到“CORS 会先发 OPTIONS 预检”,就以为所有跨域请求都会先多一次 OPTIONS。看到浏览器网络面板里有 OPTIONS,就断定服务端配置有问题。
正解实际上只有“非简单请求”才会触发预检。GET/POST 且只带安全头的请求是简单请求,直接发出、不经过 OPTIONS。判断自己的请求属于哪一类,只需对照三个条件:方法是 GET/POST/HEAD 吗?Content-Type 是三种表单类型之一吗?没有自定义请求头吗?三个都满足就是简单请求,否则才需要预检。
为何常见:网上教程大多以“跨域请求会先发 OPTIONS”切入,放大了预检的存在感。加上部分前端框架自动添加头(如 X-Requested-With),把本应简单的请求“升级”成了预检请求,更加深了这个印象。
误区很多人遇到跨域报错后,第一反应是“前端代码里补个 CORS 配置”,或者去浏览器设置里关掉跨域限制。实际上前端页面里写不出任何能“开启 CORS”的代码。
正解CORS 是浏览器的安全机制,授权规则完全由服务器端响应头决定。前端能做的只有:在 fetch 或 XMLHttpRequest 中设置是否携带凭证(credentials)。真正要解决问题,需要后端在响应中添加 Access-Control-Allow-Origin 等头部——要么业务层配置,要么网关/Nginx 层统一处理。
为何常见:错误信息总是出现在浏览器控制台,且错误堆栈会指向前端脚本的某一行,让人误以为是前端问题。此外,浏览器扩展(如“允许跨域”插件)恰好反证了“只有浏览器在管跨域”——它改的是浏览器的行为,而不是前端代码。
误区初学者配置服务端时,图省事直接写 `Access-Control-Allow-Origin: *`,心想“反正这样谁都能访问了,总不会再报错吧”。结果发现带上 Cookie 后请求反而失败了,或安全审计直接打回。
正解通配符 * 意味着“任何源都能读取响应”,一旦请求携带凭证(Cookie、TLS 证书等),浏览器会强制要求 Allow-Origin 必须是确切的源,不允许通配。同时,过多的 * 授权会放大敏感数据暴露风险。正确的做法是:能写具体源就写具体源,需要动态多源时读请求的 Origin 头回显。
为何常见:* 看起来“最省事、最不易报错”,且很多测试接口都这么写。但正式项目一旦接入登录状态或用户数据,通配符与凭证的冲突立即暴露,教训往往是踩过坑才记住。
误区后端接口明明返回了 200 和正常数据,但前端在浏览器里却拿不到,控制台报 CORS 错误。这时候 PM 和后端同事都指着网络面板说“响应都成功了,怎么还说跨域失败?”
正解CORS 的拦截发生在“响应返回之后”。服务器的请求处理已经完成、响应也到达了浏览器,但浏览器发现响应缺少必要的 Access-Control-Allow-* 头,于是充当保安的角色把数据扣下,不交给脚本。所以“服务器看到 200、浏览器看到 CORS 错误”完全不矛盾。排查时要看响应头里有没有 Access-Control-Allow-Origin。
为何常见:人的直觉是“请求成功 = 流程成功”,而浏览器恰恰在最后一步做拦截。服务端日志和客户端报错的不一致,让不熟悉 CORS 流程的人误以为问题出在传输层。
误区调试时看到网络面板里有个 `OPTIONS` 请求返回 404 或 405,第一反应是“后端接口不存在,把 OPTIONS 当成了普通接口”。于是拉着后端改路由,结果改了也没用。
正解预检请求是浏览器的“问路”,它并不携带你的业务数据,也不会执行业务逻辑。后端只需要在路由层统一响应 OPTIONS(返回 204 + CORS 头)即可,不需要为每个接口单独实现 OPTIONS 方法。很多框架(Spring、Express)都内置了 OPTIONS 处理,只要配置了 CORS 中间件就会自动响应。如果返回了 404,通常是框架层没有配置 CORS 中间件,而不是业务逻辑有问题。
为何常见:人们习惯用“接口 = 业务方法”的心智模型,难以理解 OPTIONS 这种“非业务请求”。看到 404/405 就认为是路由缺失,而实际上需要检查的是框架的 CORS 全局配置。
面试题
Q1. 以下哪个 URL 与 https://www.example.com:443/api 同源?
- A.
http://www.example.com:80/api - B.
https://www.example.com:443/index← 正确答案 - C.
https://api.example.com:443/user - D.
https://www.example.com:8443/home
答案:B。同源要求协议、域名、端口完全一致。B 与题干都是 https、www.example.com、443,只是路径不同,同源判定与路径无关。A 协议不同(http vs https),C 域名不同(api 子域 vs www),D 端口不同(8443 vs 443),均视为跨域。
Q2. 下列哪种情况属于“简单请求”,不会触发 CORS 预检?
- A.GET 请求且 Content-Type 为 application/json
- B.PUT 请求修改资源
- C.POST 请求且 Content-Type 为 application/x-www-form-urlencoded,带 Accept 头← 正确答案
- D.GET 请求携带自定义 X-Token 头
答案:C。简单请求需同时满足三个条件:方法为 GET/POST/HEAD、Content-Type 为三种表单编码之一、无自定义请求头。C 满足全部条件,是简单请求。A 的 Content-Type 是 application/json(不属于三种安全类型之一),B 的 PUT 超出方法集合,D 携带了自定义头,三者都会触发预检。
Q3. 预检请求使用什么 HTTP 方法?
- A.GET
- B.POST
- C.OPTIONS← 正确答案
- D.HEAD
答案:C。CORS 预检请求固定使用 OPTIONS 方法,浏览器通过它向服务器询问“这个跨域请求的方法和头是否被允许”。服务器返回 204 响应并附上 Access-Control-Allow-* 头。GET、POST 是业务请求的方法,HEAD 用于获取响应头但不返回响应体,都不是预检使用的方法。
Q4. 服务端要允许携带 Cookie 的跨域请求,以下哪个配置是正确的?
- A.Access-Control-Allow-Origin: * 且 Access-Control-Allow-Credentials: true
- B.Access-Control-Allow-Origin: https://front.example.com 且 Access-Control-Allow-Credentials: true← 正确答案
- C.Access-Control-Allow-Origin: https://front.example.com 且 Access-Control-Allow-Credentials: false
- D.Access-Control-Allow-Origin: * 且不加 Allow-Credentials 头
答案:B。凭证请求(携带 Cookie)要求 Allow-Origin 必须是确切的源,不能用通配符 *,同时服务端必须返回 Access-Control-Allow-Credentials: true。B 同时满足两个条件,是正确配置。A 因通配符与凭证冲突而非法,C 的 Allow-Credentials 为 false 不会放行凭证,D 完全没有允许凭证。
Q5. 关于 Access-Control-Max-Age 头的作用,下列说法正确的是?
- A.设置 Cookie 的过期时间
- B.设置预检响应结果在浏览器中的缓存时长← 正确答案
- C.设置跨域请求的最大并发数
- D.设置服务器开放跨域的时间窗口
答案:B。Access-Control-Max-Age 的值以秒为单位,告诉浏览器在指定时间内无需为同一跨域请求重复发送预检请求(OPTIONS)。它只作用于预检结果的缓存,与 Cookie 过期、并发数或服务器开放时段无关。合理设置该值可以减少网络往返,提升跨域接口性能。
Q6. 修改请求报错 "Request header field x-token is not allowed by Access-Control-Allow-Headers in preflight response",最可能的解决方案是?
- A.在前端代码中去掉 x-token 头相关的 CORS 设置
- B.在服务端响应中添加 Access-Control-Allow-Headers: x-token← 正确答案
- C.在浏览器设置中关闭同源策略
- D.将请求方法改为 GET
答案:B。该报错表示预检响应中声明的允许请求头列表不包含 x-token。解决方案是在服务端的 Access-Control-Allow-Headers 中补上该头,让预检通过。前端无法通过“去掉 CORS 设置”来解决问题,浏览器设置不属于开发范畴,改 GET 也不影响请求头是否被允许。
Q7. 前端脚本发起 fetch 请求时需要携带 Cookie,且服务端已配置 Access-Control-Allow-Credentials: true,前端还应怎么做?
- A.在 fetch 中设置 credentials: 'include'← 正确答案
- B.在 URL 中手动拼接 Cookie 参数
- C.设置 Access-Control-Allow-Origin 请求头
- D.无需任何设置,浏览器会自动携带 Cookie
答案:A。跨域请求默认不携带凭证,前端必须显式开启。fetch 中 credentials 设为 'include' 时,浏览器才会在跨域请求中附加 Cookie。B 把凭证放进 URL 既不安全也不被推荐,C 的 Allow-Origin 是响应头而非请求头,D 与“浏览器默认不携带”的规则相反。
Q8. 下列关于 CORS 简单请求的判定标准,正确的是?(多选)
- A.方法必须是 GET、POST、HEAD 之一← 正确答案
- B.Content-Type 只能是 text/plain、multipart/form-data、application/x-www-form-urlencoded← 正确答案
- C.请求头必须是 CORS 安全列表内的头部← 正确答案
- D.请求必须携带 Accept 和 Accept-Language 头
答案:ABC。简单请求的三个判定标准是方法集合、Content-Type 安全类型集合、安全请求头列表(无自定义头),对应 A、B、C。D 错误——Accept 等安全头只是“允许存在”,并非“必须携带”。
Q9. 当一个跨域请求触发了 OPTIONS 预检,预检请求的请求头中通常包含哪些字段?(多选)
- A.Access-Control-Request-Method← 正确答案
- B.Access-Control-Request-Headers← 正确答案
- C.Origin← 正确答案
- D.Access-Control-Allow-Origin
答案:ABC。预检请求由浏览器自动发出,包含 Access-Control-Request-Method(声明准备使用的方法)、Access-Control-Request-Headers(声明准备携带的自定义头)、Origin(声明来源)。D 是预检响应中的授权头,不属于请求头。预检响应中才包含 Allow-Origin、Allow-Methods、Allow-Headers 和 Max-Age。
Q10. 生产环境中,如需支持多个前端源(如开发、测试、生产环境)访问同一 API,且需要携带凭证,以下哪些做法是合理的?(多选)
- A.在后端动态读取请求的 Origin 头,回显到 Access-Control-Allow-Origin← 正确答案
- B.配置 Access-Control-Allow-Origin: * 并搭配 Allow-Credentials: true
- C.在网关层统一维护允许的源列表,动态下发响应头← 正确答案
- D.将允许的源列表写死在配置文件中,配置后不随环境变化
答案:AC。多源且带凭证时,不能使用通配符 \*,需动态读取 Origin 并校验后在 Allow-Origin 中精确回显(A),或在网关层统一做源列表管理和响应头下发(C)。B 的“通配符 + 凭证”组合非法。D 把源列表写死无法适配前端环境动态调整的需要,不是生产环境最佳实践。