cors中简单请求与复杂请求的本质区别在于浏览器是否触发预检:满足get/head/post方法且请求头和content-type受限(仅text/plain等三类)时为简单请求,直接发送;任一条件不满足则为复杂请求,需先发options预检。

CORS 中简单请求和复杂请求的本质区别,不在于“前端写了什么代码”,而在于浏览器是否认为这次跨域请求可能对服务器产生副作用。浏览器用一套明确规则自动判断——满足全部安全条件的,就直接放行;只要有一条不满足,就先发个 OPTIONS 问一句:“我待会要这么干,你答应吗?”——这就是预检。
判断依据:两套硬性条件,缺一不可
浏览器只看两个维度:
- 请求方法:仅限 GET、HEAD、POST
-
请求头与内容类型:
- 自定义 Header 字段一个都不能有(比如
X-Auth-Token、My-Custom-Id) -
Content-Type只能是以下三者之一:application/x-www-form-urlencoded、multipart/form-data、text/plain
- 自定义 Header 字段一个都不能有(比如
两项同时满足 → 简单请求;任一不满足 → 复杂请求。
行为差异:一次通信 vs 两次通信
简单请求本质是“低风险直连”:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 浏览器自动加
Origin请求头,直接发出去 - 服务器只需在响应中返回
Access-Control-Allow-Origin等必要头,浏览器检查后决定是否把响应体交给 JS
复杂请求本质是“先申请、再操作”:
- 浏览器先发一个
OPTIONS预检请求,不含请求体,只带:Origin、Access-Control-Request-Method(如 PUT)、Access-Control-Request-Headers(如Authorization, X-Trace-ID) - 服务器必须明确回应允许这些方法和头,例如:
Access-Control-Allow-Methods: PUT, DELETEAccess-Control-Allow-Headers: Authorization, X-Trace-ID - 只有预检成功,浏览器才发出真正的请求
为什么这样设计?安全逻辑很清晰
CORS 不是为“跨域而跨域”,而是为“可控地跨域”。它的底层假设是:
- GET/POST 表单提交、纯文本或表单编码的数据,历史上长期被 HTML 原生标签(如
<form></form>、<img>)随意发起,不具备破坏性 - 但带自定义头、用 PUT/DELETE、传 JSON 的请求,在旧机制下无法被 HTML 标签触发,属于“JS 主动发起的高权限操作”,必须由服务器显式授权
所以预检不是多此一举,而是把“谁有权改数据”的决策权,交还给服务端。
常见踩坑点:你以为是简单,其实已被浏览器判定为复杂
- 哪怕只是加了一行
headers: { 'X-Requested-With': 'XMLHttpRequest' }—— 就触发预检 - 用
fetch发 POST 且Content-Type: application/json—— 必然复杂请求 - 设置了
credentials: 'include',但响应头里Access-Control-Allow-Origin: *—— 直接失败(带凭证时 Origin 不能为通配符)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










