浏览器同源策略
理解同源策略的限制范围和跨域解决方案
问题
什么是浏览器同源策略 (Same-Origin Policy)?它限制了哪些行为?如何解决跨域问题?
解答
什么是同源
两个 URL 的协议、域名、端口完全相同,才算同源。
// 假设当前页面是 https://example.com:443/page
// 同源
'https://example.com/api' // ✅ 协议、域名、端口都相同
// 不同源
'http://example.com/api' // ❌ 协议不同 (http vs https)
'https://api.example.com/data' // ❌ 域名不同 (子域名也算不同)
'https://example.com:8080/api' // ❌ 端口不同
同源策略限制了什么
| 限制类型 | 具体表现 |
|---|---|
| DOM 访问 | 无法读取跨域 iframe 的 DOM |
| Cookie/Storage | 无法读取跨域页面的 Cookie、localStorage |
| AJAX 请求 | 无法发送跨域 XMLHttpRequest/Fetch 请求 |
// 尝试访问跨域 iframe 的 DOM - 会报错
const iframe = document.querySelector('iframe')
iframe.contentWindow.document // ❌ Blocked by SOP
// 跨域 AJAX 请求 - 会被拦截
fetch('https://other-domain.com/api')
.then(res => res.json())
.catch(err => console.log('跨域请求被阻止'))
跨域解决方案
1. CORS(推荐)
服务端设置响应头允许跨域:
// Node.js Express 示例
app.use((req, res, next) => {
// 允许的源
res.setHeader('Access-Control-Allow-Origin', 'https://example.com')
// 允许的方法
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
// 允许的请求头
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
// 允许携带 Cookie
res.setHeader('Access-Control-Allow-Credentials', 'true')
// 处理预检请求
if (req.method === 'OPTIONS') {
return res.sendStatus(204)
}
next()
})
// 前端请求携带凭证
fetch('https://api.example.com/data', {
credentials: 'include' // 携带 Cookie
})
2. JSONP(仅支持 GET)
利用 <script> 标签不受同源策略限制的特性:
// 封装 JSONP 函数
function jsonp(url, callbackName = 'callback') {
return new Promise((resolve, reject) => {
// 创建全局回调函数
const fnName = `jsonp_${Date.now()}`
window[fnName] = (data) => {
resolve(data)
document.body.removeChild(script)
delete window[fnName]
}
// 创建 script 标签
const script = document.createElement('script')
script.src = `${url}?${callbackName}=${fnName}`
script.onerror = reject
document.body.appendChild(script)
})
}
// 使用
jsonp('https://api.example.com/data')
.then(data => console.log(data))
3. 代理服务器
开发环境使用 webpack/vite 代理:
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
}
4. postMessage(跨窗口通信)
// 父页面发送消息
const iframe = document.querySelector('iframe')
iframe.contentWindow.postMessage({ type: 'greeting', data: 'hello' }, 'https://child.com')
// 子页面接收消息
window.addEventListener('message', (event) => {
// 验证来源
if (event.origin !== 'https://parent.com') return
console.log(event.data) // { type: 'greeting', data: 'hello' }
// 回复消息
event.source.postMessage({ type: 'reply', data: 'hi' }, event.origin)
})
关键点
- 同源定义:协议 + 域名 + 端口,三者完全一致
- 限制范围:DOM 访问、Cookie/Storage 读取、AJAX 请求
- CORS:服务端设置
Access-Control-Allow-*响应头,是标准解决方案 - JSONP:利用 script 标签绕过限制,只支持 GET 请求
- 预检请求:非简单请求会先发 OPTIONS 请求询问服务器是否允许
目录