跨域

18 阅读2分钟

同源策略:协议号-域名-端口号完全一致 12

  • 跨域问题是由浏览器的同源策略引
  • 同源策略的目的是保护用户的隐私和数据安全,防止恶意网站获取用户的敏感信息或进行未授权的操作
  • 减少网络攻击:跨站脚本攻击XSS/跨站请求伪造CSRF

解决方案

1. JSONP [JSON with Padding]

<script>可以跨域加载资源

前端动态创建<script>标签,并且设置其src为url[跨域]+回调函数[用于处理服务端返回的数据]: http://xxx?cb=callback

优:
  - 简单易用
  - 兼容性好
  - 支持跨域请求
  
劣:
  - 安全性问题:因为是在前端通过动态加载脚本实现的,有被恶意注入脚本的风险
  - 只支持Get请求
  - 依赖服务端支持

2. 常用CORS [跨域资源共享]

一种机制

服务器通过设置响应头告知浏览器不要拒绝接受服务器的响应
- Access-Control-Allow-Origin: http://xxx:3000[特定域名] / *[所有域名]
- Access-Control-Allow-Methods: 指定允许的请求方法
- Access-Control-Allow-Headers: 指定允许的自定义请求头

3. domain

- 域名需满足domain方法的限制,即二级域名相同:example.com
- 父级页面需要在设置document.domain之前定义需要共享的变量/对象
- 子级页面可以通过 window.parent 来访问父级页面的属性/变量,但需要确保父级页面已经加载完成并且两者的域名设置已生效。

例:
页面A:parent.example.com
页面B:child.example.com

实现A与B跨域通信

// parent
<body>
  <iframe src="http://child.example.com/child.html"></iframe>
  <script>
    var sharingMsgFormParent = 'xxxx';
    document.domain = 'example.com';
  </script>
</body>

// child
<script>
  var sharingMsgFromChild = 'from me';
  document.domain = 'example';
  
  // 访问父级页面的变量
  window.parent.sharingMsgFormParent
</script>

4. postMessage

配合<iframe>使用,利用浏览器的同源策略,将不同域嵌套到不同的iframe中,通过postMessage实现跨文档通信

<body>
  <iframe id="iframe" src="http://test.com/test"></iframe>
  <script>
    const iframe = document.getElementById('iframe');
    
    iframe.onload() {
      const data = { xx: 1 };
      
      // 发送
      iframe.contentWindow.postMessage(JSON.stringify(data), 'http://test.com');
    }
    
    // 监听来自其他页面的消息
    window.addEventListener('message', (e) => {
      // e.data
    })
  </script>
</body>

优:
  - 安全性好:能够避免XSS
  - 灵活性高
  - 适用性广泛
  
劣:
  - 复杂性高
  - 性能消耗大:使用iframe可能会引入额外的网络请求和资源加载,有页面加载时的性能有一定影响
  - 兼容性差: 旧版本浏览器可能不支持

5. 常用反向代理

开发环境:通过脚手架或webpack配置devSever下的proxy选项,将/api开头的请求转发到真实服务器上
生产环境:使用nginx配置反向代理

// webpack.config.js
module.exports = {
  devServer: {
    // 一对一
     api: {
       target: 'http://example.com',
       changeOrigin: true, // target是域名的话,需要设为true
       secure: false, // 设置支持https协议的代理
     },
     // 多个特定路径代理到同一个目标
     proxy: [
       {
         context: ['/api1', '/api2'],
         target: 'http://example.com'
       }
     ]
  }
}

参考:
面试官:请你说出三种解决跨域的方法