怎么实现跨域

Posted by CodingWithAlice on October 24, 2024

怎么实现跨域

一、定义

指的是 浏览器 不能执行其他网站的脚本。浏览器执行javascript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行。(同源策略)

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。

同源策略
  • 定义:同源策略是一种用于限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互的安全机制
  • 限制:
    • DOM访问:不同源的文档之间不能直接访问对方的 DOM
    • 网络请求 + <img><link><script>等标签:发起的请求只能访问当前页面同源(协议、域名、端口)的资源
    • Cookie、LocalStorage、IndexedDB 等存储性内容:不能彼此访问

二、解决办法

总结:

  • ①CORS - 跨域资源共享 支持所有类型的HTTP请求,是跨域HTTP请求的【根本解决方案

  • ②JSONP『只支持GET请求』,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据

  • ③代理服务器,主要是通过 同源策略对服务器不加限制,浏览器和代理服务器同源即可,最简单
  • ④ postMessage ⑤ websocket协议,有一些缺点,但也可用;还有一些通过 iframe 传递的方法,可实现,但不实用,不做记录
① CORS - 跨域资源共享 - 服务端设置

(Cross-Origin Resource Sharing)

核心:在服务端设置响应头

  • Access-Control-Allow-Origin - 该属性表示 哪些域名可以访问资源

详细情况补充:简单请求和复杂请求(使用 option 预检请求查询是否允许跨域)

  • 简单请求定义:使用GET、HEAD、POST方法之一 且 Content-Type 的值是 text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一

  • 复杂请求定义:除了简单请求,就是复杂请求

​ 复杂请求会在正式通信之前,增加一次HTTP查询请求 - 称为“预检”请求,该请求是 option 方法的,通过该请求来知道 服务端是否允许跨域请求

res.setHeader('Access-Control-Allow-Methods', 'PUT')
res.setHeader('Access-Control-Max-Age', 6)
// 核心
if (req.method === 'OPTIONS') {	// OPTIONS请求不做任何处理
  res.end() 
}
app.put('/getData', function(req, res) {	
  res.end('我爱你')
})
② JSONP只支持GET请求 - 不安全可能会遭受XSS攻击

核心:利用了<script>标签 src 属性可以跨域加载资源的特性

  • step1客户端:通过动态创建<script>标签,并设置其src属性为跨域的 URL,同时指定一个回调函数
  • step2服务端:将数据包 装在指定的回调函数中,作为 js 代码返回
  • step3客户端:加载返回的 js 时,执行回调函数,将服务器数据传递给前端
<script>
    function handleData(data) {
        console.log(data);
    }
</script>
<script src="https://example.com/data?callback=handleData"></script>
// 服务器端返回的内容可能是类似:handleData({name: 'example'})这样的形式
// 浏览器会执行这个回调函数,从而将数据传递给前端
③ 代理服务器【最简单】

核心:代理服务器和前端在同一域下,所以不存在跨域问题

实现方式:在前端和目标服务器之间设置一个 代理服务器。前端向代理服务器发送请求,代理服务器再将请求转发到目标服务器,然后将目标服务器的响应返回给前端。

// nginx - proxy服务器
server {
    listen 81;
    server_name  www.domain1.com;
    location / {
        proxy_pass   http://www.domain2.com:8080;  //反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com;  // 修改cookie里域名
        index  index.html index.htm;
        add_header Access-Control-Allow-Origin http://www.domain1.com;  
        add_header Access-Control-Allow-Credentials true;
    }
}
// 以 Vue.js 为例,可以在vue.config.js文件中设置代理
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'https://example.com',
        changeOrigin: true,
      },
    },
  },
};
④ postMessage - 一个窗口向另一个窗口发送/接收消息
  • 缺点:适用于两个窗口之间通信,对于 ajax 请求跨域问题不能直接解决
// 一个页面传递
const targetWindow = window.open('https://another-example.com/pageB');
targetWindow.postMessage('Hello from page A!', 'https://another-example.com');
// 另一个页面接收
window.addEventListener('message', function(e) {
    if (event.origin === 'https://example.com') {
        console.log('Received message:', e.data);
        // 可以在这里对收到的消息进行处理
    }
});
⑤ websocket协议 - 协议本身并不受同源策略的限制

实现方式: WebSocket 是一种双向通信协议,在建立连接(服务器配置)之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据

  • 缺点:如果只是需要进行简单的数据获取或提交,使用传统的 AJAX 请求或 CORS 可能更加方便;需要服务器配置支持
const WebSocket = require('ws');

const wss = new WebSocket.Server({
  port: 8080,
  // 允许跨域连接
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST',
    'Access-Control-Allow-Headers': 'Content-Type'
  }
});