同源策略和 CORS 跨域是什么?
简化版
同源策略是浏览器的安全机制,协议、域名、端口都相同才算同源。不同源的脚本读取响应会受限制。CORS 是服务器通过响应头允许指定来源访问资源的机制,常见头包括 Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers。
详细版
同源策略限制的是浏览器环境中的跨源读取,目的是防止恶意网站读取用户在其他站点的敏感数据。
跨域不是请求一定发不出去,而是浏览器不允许前端 JS 读取不被授权的响应。
CORS 的核心是服务端声明允许谁访问。简单请求会直接发送,浏览器检查响应头;复杂请求会先发送 OPTIONS 预检请求,确认允许后再发送真实请求。
开发环境也常用代理解决跨域,但生产环境根本方案仍然是服务端正确配置 CORS。
完整版教学
一、什么是同源
同源要求三者都相同:
- 协议相同,例如
https。 - 域名相同,例如
example.com。 - 端口相同,例如
443。
只要有一个不同,就属于跨源。
同源策略保护的是用户数据。例如用户登录了银行网站,如果没有同源策略,恶意网站就可能用用户浏览器身份请求银行数据并读取结果。
二、CORS 的工作方式
CORS 的全称是跨源资源共享。它不是前端单方面能解决的问题,而是浏览器和服务器共同遵守的协议。
服务器返回:
Access-Control-Allow-Origin: https://example.com
浏览器看到当前页面来源被允许,才把响应交给前端 JS。
如果请求包含自定义头、非简单方法或特殊 content-type,浏览器会先发 OPTIONS 预检请求。
三、携带 cookie 的跨域
如果跨域请求要携带 cookie,需要前端设置 credentials,服务端也要允许凭证。
同时 Access-Control-Allow-Origin 不能简单写 *,必须指定明确来源。
这也是很多登录态跨域问题的根源:请求发了,接口也返回了,但浏览器因为 CORS 规则不把响应交给 JS。
四、面试追问与工程落地
面试官常问:“JSONP 为什么能跨域?”
因为同源策略不限制 <script> 加载跨域脚本。JSONP 利用脚本标签加载一段函数调用。但它只支持 GET,安全性和错误处理都比较弱,现代项目更推荐 CORS。
工程中,开发代理只是本地绕过浏览器跨域限制的便利方案。上线后仍然需要后端网关、API 服务或 CDN 正确配置跨域响应头。
五、简单请求、预检与凭证矩阵
“简单请求”不是不受 CORS 管理,而是浏览器可直接发送实际请求,再检查响应是否允许脚本读取。非简单方法、非 safelisted 请求头或特定 Content-Type 会先发不带普通请求凭证的 OPTIONS 预检,询问服务端允许的方法和头。
| 场景 | 前端 credentials | 服务端允许来源 | 结果 |
|---|---|---|---|
| 公共无凭证 API | omit/默认适用值 | * | 可读取 |
| 跨源 Cookie | include | 明确 Origin + Allow-Credentials | 可读取 |
| 跨源 Cookie | include | * | 浏览器阻止读取 |
| 非允许来源 | 任意 | 无匹配 ACAO | 浏览器阻止读取 |
Access-Control-Allow-Origin: https://app.example
Access-Control-Allow-Credentials: true
Vary: Origin
若服务端按白名单动态回显 Origin,必须真实校验而不是原样反射,并配 Vary: Origin 防共享缓存串用响应。预检结果可用 Access-Control-Max-Age 缓存,但浏览器有自己的上限;缓存 600 秒意味着同类请求在 10 分钟内可能免去重复 OPTIONS,不代表实际请求被缓存。
六、CORS、CSRF 与跨源能力边界
同源策略主要限制跨源读取和脚本交互,并不禁止所有跨源资源嵌入或请求。表单、图片、链接等可发起部分跨站请求,所以“攻击者读不到响应”不等于“状态改变不会发生”;CORS 不是 CSRF 防护,修改类接口仍要 SameSite、CSRF token 和 Origin 校验。
反向代理把浏览器看到的 API 变成同源,可以消除前端 CORS 流程,但代理仍需安全地认证上游、过滤头和限制目标地址。开发服务器代理只是本地拓扑,不能解释成“前端修好了服务端跨域”。
JSONP 利用 classic script 可跨源加载并执行,只支持 GET 风格且把第三方响应当代码执行,风险和错误语义都弱。postMessage 适合窗口/iframe 跨源通信,但接收端必须检查 event.origin,发送时避免把 targetOrigin 随意写成 *。
安全心法:CORS 决定“哪个 Origin 的脚本能读响应”,CSRF 防护决定“跨站请求能不能以用户身份改变状态”,两者不能互相替代。
七、常见误区与追问
- 误区:跨域请求一定不会发送到服务器。 简单请求通常先发送,只是响应不满足 CORS 时不会交给脚本读取。
- 误区:
Access-Control-Allow-Origin: *可以和 Cookie 凭证一起使用。 凭证请求要求明确来源,并配Access-Control-Allow-Credentials: true。 - 误区:配置 CORS 后就不会遭受 CSRF。 表单等请求不依赖 CORS 读取权限,状态变更仍要专门的 CSRF 防护。
- 追问:哪些变化常触发预检? 非简单方法、自定义头或不在 safelist 的 Content-Type 等会让浏览器先发 OPTIONS。
- 追问:预检请求会带业务 Cookie 吗? 按 CORS/Fetch 模型,预检本身不带普通请求凭证,实际凭证请求由双方许可决定。
- 追问:为什么动态 Origin 响应要加
Vary: Origin? 共享缓存需要按 Origin 区分响应,否则可能把允许 A 的头复用给 B。 - 追问:如何排查“Network 200 但 JS 报 CORS”? 检查实际/预检响应头、重定向、credentials、Origin 精确匹配和控制台错误,而不只看状态码。
八、加强记忆
同源用 scheme、host、port 三元组判断;CORS 是浏览器依据服务端响应头决定能否把跨源响应交给脚本。简单请求直接发后检查,非简单请求先预检;带凭证时客户端 include、明确允许来源和 Allow-Credentials 必须同时成立。最后牢记 CORS 管读取,不负责阻止 CSRF 状态变更。