同源策略(Same-Origin Policy)是浏览器最核心的安全机制,也是前端开发者绕不开的一道坎。它规定了一个源(origin)的脚本只能读取、操作同源文档的资源和数据,而不能随意访问其他源的资源。理解它是理解所有跨域方案的起点。
什么是“同源”
一个“源”由三部分组成:协议(scheme)、域名(host)、端口号(port)。只要这三者完全一致,就属于同源;三者中任何一项不同,就是跨域(跨源)。
典型示例(设当前页面源为 http://www.example.com:80):
| 对比 URL | 是否同源 | 原因 |
|---|---|---|
| http://www.example.com/app | ✅ 同源 | 路径不同不影响源 |
| https://www.example.com | ❌ 跨域 | 协议不同(http vs https) |
| http://example.com | ❌ 跨域 | 域名不同(www 子域也算不同) |
| http://www.example.com:8080 | ❌ 跨域 | 端口不同(80 vs 8080) |
| http://www.example.com/api | ✅ 同源 | 路径不参与源比较 |
特例:IE 浏览器的同源策略不比较端口,而所有现代浏览器都严格执行三要素完全匹配。
本质:浏览器强加的安全契约
同源策略的出发点非常朴素:保护用户数据不被恶意网站窃取或篡改。
试想没有同源策略,你访问了一个恶意网站 evil.com,该网站可以通过脚本:
- 读取你已登录的
bank.com的页面内容,偷走余额信息 - 向
bank.com发送转账请求,带上你的登录态 - 读取你邮箱中的私密邮件
为了防止这种跨站读取和篡改,浏览器给每个源分配了独立的沙箱,一个源内的脚本无法直接触及另一个源的 DOM、Cookie、存储数据和网络响应内容。同源策略是浏览器安全的基石,其核心原则是:跨域写操作通常允许,跨域读操作严格限制。
具体限制了什么
同源策略的限制主要体现在三个层面:
- DOM 访问限制
不同源的页面之间无法通过 JavaScript 获取彼此的 DOM。例如,父页面中通过 <iframe> 嵌入了跨域子页面,父页面上的脚本无法读取 iframe.contentWindow.document,会抛出跨域安全错误。
- 数据存储隔离
Cookie、localStorage、sessionStorage、IndexedDB 均以源为边界。bank.com 设置的 Cookie 无法被 evil.com 的脚本读取,浏览器也禁止跨源访问存储数据。这保证了用户凭证和本地数据不会泄漏给第三方源。
- AJAX 请求响应读取限制
这里有一个常被误解的点:同源策略并不阻止浏览器发起跨域请求,它只禁止前端脚本读取跨域请求的响应。
例如,你可以用 XMLHttpRequest 或 fetch 向 api.other.com 发送一个 POST 请求,请求成功发出并到达服务器,但浏览器检查到响应来自不同源,会拒绝将响应数据交给你的 JavaScript 代码,直接在控制台报错。这种限制强迫前后端分离的 Web 应用必须使用跨域方案。
哪些行为不受同源策略限制
同源策略并非铁板一块,为了兼顾功能和体验,浏览器为某些资源加载开辟了例外:
<script>标签加载跨域 JS:这是 JSONP 的实现基础,但加载到的脚本会直接在当前源执行,存在安全隐患。<img>标签加载图片:跨域图片可以正常显示,但无法用 Canvas 读取像素数据(会污染画布)。<link>标签加载 CSS:跨域样式表可以正常应用,但 CSSOM 的读取受限。<video>、<audio>、<iframe>加载资源:可以跨域加载,但脚本无法直接读取内容。- 表单提交:
<form>可以直接跨域提交,这是典型的跨域写操作,一直不受限制。 <a>标签导航:用户点击链接可以跳转到任何源,不违反同源策略。
这些例外本质上是“允许跨域引用静态资源,但限制脚本读取敏感数据”的折中方案。
对实际开发的影响
前后端分离成为主流的今天,API 通常部署在不同域名或端口下,跨域问题几乎是每个项目的必考题。深刻理解同源策略后,你会发现所有跨域方案(CORS、JSONP、代理、postMessage 等)都是在遵守或绕过这个安全策略——要么让服务器声明信任特定源(CORS),要么通过服务器中转避开浏览器限制(代理),要么使用不受策略限制的通信通道(postMessage)。这也是 16.4 节后续内容要逐一剖析的重点。