1. 同源策略的本质与安全边界
同源策略(Same-Origin Policy)是现代浏览器安全架构的基石,它定义了不同来源的文档或脚本如何相互交互的规则。这个策略的核心在于:只有当两个URL的协议(protocol)、主机名(host)和端口(port)完全相同时,才被认为是同源。
1.1 同源判定的三要素
- 协议:比如https://和http://被视为不同源
- 主机名:api.example.com和www.example.com属于不同源
- 端口:example.com:80和example.com:8080属于不同源(默认端口80可省略)
实际案例:假设当前页面URL是
https://store.company.com/dir/page.html
https://store.company.com/dir2/other.html→ 同源(仅路径不同)http://store.company.com/dir/page.html→ 不同源(协议不同)https://news.company.com/dir/page.html→ 不同源(主机不同)
1.2 安全隔离的必要性
同源策略主要防范两类风险:
- 数据窃取:阻止恶意网站读取用户在其他站点的敏感数据(如登录状态)
- CSRF攻击:防止第三方站点利用用户已认证的状态执行非预期操作
我在实际项目中发现,很多开发者会误认为子域名(如a.example.com和b.example.com)自动属于同源,这种误解常导致安全配置疏漏。正确的做法是显式设置document.domain或使用CORS。
2. 跨域交互的三种类型
2.1 跨域写操作(Cross-origin writes)
通常被允许的行为包括:
- 链接跳转(
<a>标签) - 表单提交(需注意Content-Type)
- 重定向请求
特殊限制:某些HTTP方法(如PUT/DELETE)需要预检请求(Preflight)
2.2 跨域资源嵌入(Cross-origin embedding)
浏览器默认允许的嵌入资源:
<!-- 典型跨域嵌入示例 --> <script src="https://cdn.example.com/library.js"></script> <link rel="stylesheet" href="https://static.example.com/style.css"> <img src="https://media.example.com/image.jpg"> <iframe src="https://widget.example.com"></iframe>关键细节:虽然能加载这些资源,但父页面无法直接读取嵌入资源的内容(除非资源方明确允许)
2.3 跨域读操作(Cross-origin reads)
默认严格禁止,但存在一些"巧妙"的获取方式:
- 图片宽高:通过JavaScript读取
<img>元素的naturalWidth属性 - 脚本错误:捕获跨域脚本的语法错误信息
- 资源可用性:通过onload/onerror事件判断资源是否存在
3. 突破同源限制的实践方案
3.1 CORS(跨源资源共享)
现代Web开发的标准解决方案,需要服务端配合设置响应头:
Access-Control-Allow-Origin: https://yourdomain.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: Content-Type Access-Control-Max-Age: 86400常见坑点:
- 带凭证的请求(credentials)需要额外配置
- 非简单请求(如Content-Type为application/json)会触发预检
- 缓存控制不当可能导致预检请求过多
3.2 JSONP(历史方案)
利用<script>标签不受同源限制的特性:
function handleResponse(data) { console.log('Received:', data); } const script = document.createElement('script'); script.src = 'https://api.example.com/data?callback=handleResponse'; document.body.appendChild(script);局限性:
- 仅支持GET请求
- 存在XSS风险
- 错误处理困难
3.3 postMessage API
跨文档通信的安全方式:
// 发送方 iframe.contentWindow.postMessage( { key: 'value' }, 'https://target-origin.com' ); // 接收方 window.addEventListener('message', (event) => { if (event.origin !== 'https://trusted-origin.com') return; console.log('Received:', event.data); });4. 特殊场景处理技巧
4.1 本地开发环境配置
开发时常见的跨域问题解决方案:
// webpack配置示例 devServer: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, pathRewrite: { '^/api': '' } } } }4.2 跨域cookie处理
需要同时满足三个条件:
- 服务端设置
Access-Control-Allow-Credentials: true - 客户端设置
withCredentials: true Access-Control-Allow-Origin不能为*
4.3 WebSocket的特殊性
WebSocket不受同源策略限制,但可以通过Origin头进行验证:
const socket = new WebSocket('wss://example.com'); socket.onopen = () => { console.log('Connection established'); };5. 安全加固建议
5.1 防御CSRF攻击
必做的防护措施:
<!-- 表单中添加Token --> <form action="/transfer" method="POST"> <input type="hidden" name="_csrf" value="随机令牌值"> <!-- 其他表单字段 --> </form>配套服务端验证:
app.post('/transfer', (req, res) => { if (!validateCSRFToken(req.body._csrf)) { return res.status(403).send('Invalid CSRF token'); } // 处理正常业务逻辑 });5.2 内容安全策略(CSP)
增强型防护配置示例:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; img-src *; style-src 'self' 'unsafe-inline'; frame-ancestors 'none';5.3 现代浏览器安全特性
推荐组合使用:
- COEP (Cross-Origin-Embedder-Policy)
- COOP (Cross-Origin-Opener-Policy)
- CORP (Cross-Origin-Resource-Policy)
6. 调试与问题排查
6.1 常见错误分析
CORS header missing:服务端未设置必要响应头Credentials not supported:Access-Control-Allow-Origin包含通配符*Preflight failure:OPTIONS请求未正确处理
6.2 Chrome开发者工具技巧
- 使用Network面板查看请求头/响应头
- 开启
Disable cache选项避免缓存干扰 - 通过
chrome://flags/#block-insecure-private-network-requests调试本地网络请求
6.3 服务端日志检查要点
- 确认OPTIONS请求是否到达服务端
- 检查Access-Control-*头是否正确返回
- 验证预检请求的Access-Control-Max-Age缓存设置
在实际项目中,我建议建立一个跨域问题检查清单,包含上述所有关键点。每次遇到跨域问题时系统性地排查,可以节省大量调试时间。同时要注意,安全策略的配置应该遵循最小权限原则,避免过度放宽限制导致安全风险。