同源策略(Same-Origin Policy, SOP)是浏览器最重要的安全机制之一,它限制了从一个源加载的文档或脚本如何与另一个源的资源进行交互。iframe 创建了一个独立的浏览上下文,其内容必须遵守同源策略。
同源的定义
两个页面具有相同的源,必须同时满足以下三个条件:
- 协议相同: 如都使用 https://
- 域名相同: 如都使用 example.com
- 端口相同: 如都使用 443(https 默认端口)
同源示例
javascript// 以下 URL 与 https://example.com/page.html 同源 https://example.com/other.html https://example.com/sub/page.html // 以下 URL 与 https://example.com/page.html 不同源 https://www.example.com/page.html // 子域名不同 http://example.com/page.html // 协议不同 https://example.com:8080/page.html // 端口不同
iframe 同源策略的限制
1. DOM 访问限制
javascript// 父页面 const iframe = document.getElementById('myIframe'); // 同源:可以访问 if (iframe.contentDocument) { const title = iframe.contentDocument.title; } // 不同源:无法访问,会抛出 SecurityError try { const title = iframe.contentDocument.title; } catch (e) { console.error('跨域访问被拒绝:', e); }
2. JavaScript 访问限制
javascript// 父页面 const iframe = document.getElementById('myIframe'); // 同源:可以调用 iframe 内的函数 if (iframe.contentWindow) { iframe.contentWindow.someFunction(); } // 不同源:无法调用 try { iframe.contentWindow.someFunction(); } catch (e) { console.error('跨域调用被拒绝:', e); }
3. Cookie 和 LocalStorage 访问限制
javascript// iframe 内部代码 // 同源:可以访问父页面的 Cookie const cookies = document.cookie; // 不同源:无法访问父页面的 Cookie // 只能访问自己源的 Cookie
跨域 iframe 的解决方案
1. postMessage API(推荐)
javascript// 父页面 const iframe = document.getElementById('myIframe'); // 发送消息到 iframe iframe.postMessage({ type: 'getData', payload: { key: 'value' } }, 'https://child-domain.com'); // 接收 iframe 的消息 window.addEventListener('message', (event) => { // 验证消息来源 if (event.origin !== 'https://child-domain.com') { return; } console.log('收到 iframe 消息:', event.data); }); // iframe 内部代码 // 接收父页面消息 window.addEventListener('message', (event) => { if (event.origin !== 'https://parent-domain.com') { return; } // 处理消息 if (event.data.type === 'getData') { const result = processData(event.data.payload); // 发送响应 window.parent.postMessage({ type: 'dataResponse', payload: result }, 'https://parent-domain.com'); } });
2. document.domain(仅限同主域名)
javascript// 父页面 https://www.example.com document.domain = 'example.com'; // iframe https://sub.example.com document.domain = 'example.com'; // 现在可以相互访问 const iframe = document.getElementById('myIframe'); const iframeContent = iframe.contentDocument;
限制:
- 只适用于同主域名下的子域名
- 不适用于完全不同的域名
- 不适用于 HTTPS 和 HTTP 混合使用
3. CORS(跨域资源共享)
javascript// 服务器端设置 CORS 头 Access-Control-Allow-Origin: https://parent-domain.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: Content-Type Access-Control-Allow-Credentials: true // 前端使用 fetch fetch('https://api.example.com/data', { credentials: 'include' }) .then(response => response.json()) .then(data => console.log(data));
4. JSONP(已过时,不推荐)
javascript// 不推荐使用 JSONP,存在安全风险 // 使用 postMessage 或 CORS 代替
iframe 同源策略的安全最佳实践
1. 始终验证消息来源
javascriptwindow.addEventListener('message', (event) => { // 严格验证来源 const allowedOrigins = [ 'https://trusted-domain.com', 'https://another-trusted-domain.com' ]; if (!allowedOrigins.includes(event.origin)) { console.warn('拒绝来自未授权来源的消息:', event.origin); return; } // 验证数据格式 if (!event.data || typeof event.data !== 'object') { return; } // 处理消息 handleMessage(event.data); });
2. 使用具体的 targetOrigin
javascript// 不推荐:允许所有来源 iframe.postMessage(data, '*'); // 推荐:指定具体来源 iframe.postMessage(data, 'https://trusted-domain.com');
3. 限制 iframe 权限
html<!-- 使用 sandbox 限制权限 --> <iframe src="https://external-content.com" sandbox="allow-scripts allow-same-origin"> </iframe>
4. 使用 CSP(内容安全策略)
httpContent-Security-Policy: frame-src 'self' https://trusted-domain.com; Content-Security-Policy: child-src 'self' https://trusted-domain.com;
5. 避免敏感数据传输
javascript// 不推荐:通过 postMessage 传输敏感数据 iframe.postMessage({ type: 'auth', token: 'sensitive-token' }, 'https://external-domain.com'); // 推荐:使用安全的认证流程 // 1. 父页面发起认证请求 // 2. iframe 处理认证 // 3. 通过安全通道返回认证结果
iframe 同源策略的常见问题
1. 如何检测 iframe 是否同源?
javascriptfunction isSameOrigin(iframe) { try { // 尝试访问 contentDocument const doc = iframe.contentDocument; return true; } catch (e) { // 访问被拒绝,说明跨域 return false; } } const iframe = document.getElementById('myIframe'); if (isSameOrigin(iframe)) { console.log('iframe 同源'); } else { console.log('iframe 跨域'); }
2. 如何获取 iframe 的实际源?
javascriptconst iframe = document.getElementById('myIframe'); console.log('iframe src:', iframe.src); console.log('iframe origin:', new URL(iframe.src).origin);
3. 如何处理 iframe 加载错误?
html<iframe src="https://example.com/content" onerror="handleIframeError(this)" onload="handleIframeLoad(this)"> </iframe> <script> function handleIframeError(iframe) { console.error('iframe 加载失败:', iframe.src); iframe.src = '/error-page.html'; } function handleIframeLoad(iframe) { console.log('iframe 加载成功:', iframe.src); } </script>
iframe 同源策略的浏览器兼容性
所有现代浏览器都支持 iframe 同源策略:
- Chrome(所有版本)
- Firefox(所有版本)
- Safari(所有版本)
- Edge(所有版本)
- IE(所有版本)
总结
iframe 同源策略是保护网页安全的重要机制。关键要点:
- 理解同源定义: 协议、域名、端口必须相同
- 遵守访问限制: 跨域 iframe 无法直接访问 DOM 和 JavaScript
- 使用安全通信: 使用 postMessage API 进行跨域通信
- 验证消息来源: 始终验证 postMessage 的来源
- 限制 iframe 权限: 使用 sandbox 和 CSP 限制 iframe 权限
- 避免敏感数据传输: 不要通过不安全的方式传输敏感数据