跨域的方式:解决Web开发中的同源策略难题

611 阅读4分钟

距离上次写文章已经是在上次了,历经了两三个星期的面试,感觉自己在逐步提升,身边的小伙伴也成功拿下了字节,在替他高兴的同时也时刻告诫自己是时候该冲刺了。今天主要分享的是面试中常问的一道题:如何实现跨域。

同源策略

浏览器为了保证用户的隐私和数据安全,实施了一种称为“同源策略”的安全机制。这意味着,只有当请求的来源(包括协议、域名和端口)与请求的目标完全一致时,才能获取到目标资源。

例如:https://192.168.3.0:3000/home
其中,https便是协议,192.168.3.0是域名,3000是端口,之后的便是路径

同源策略(Same-origin policy)是一种约定俗成的安全措施,旨在防止恶意脚本从一个站点读取另一个站点的数据。这项政策确保了只有来自同一源的请求才能访问相应的资源,而不同源的请求则被浏览器拦截。因此,对于需要从不同源加载资源或数据的应用来说,必须采取额外的技术手段来突破这一限制。

跨域解决方案

JSONP

<img src="https://img2.baidu.com/it/u=1490602810,3032519025&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1724432400&t=07e35fb47df14aae4c5426372d8dc268"
    alt="">

首先,我们发现对于img中的src是不受同源策略的影响,JSONP也正好借助的是这一点,它利用了 <script> 标签的 src 属性不受同源策略限制的特点,前端可以通过设置 src 属性发起GET请求。

<script>
    function jsonp(url, cb) {
      return new Promise(function (resolve, reject) {
        const script = document.createElement('script');
        window[cb] = function (data) {   // callback()
          resolve(data)
        }
        script.src = `${url}?cb=${cb}`;
        document.body.appendChild(script);
      });
    }
    jsonp('http://localhost:3000', 'callback').then(res => {
      console.log(res);
    })
  </script>

src中,我们携带callback函数,并在windows全局添加它。

const http = require('http');
http.createServer(function (req, res) {
  const query = new URL(req.url, `http://${req.headers.host}`).searchParams
  if (query.get('cb')) {
    const cb = query.get('cb')  // 'callback'
    const data = 'hello world 12312312123'
    const result = `${cb}("${data}")`   // 'callback("hello world")'
    res.end(result)
  }
}).listen(3000);

其中query拿到的是存储callbackmap对象,后端接收到请求后,如果存在cb的话,将需要返回给前端的数据作为callback的参数一起以字符串的形式传给前端。我们之前在windows全局添加了该方法,所以在windows环境中会将该字符串读成callback函数,直接执行拿到数据。

CORS

const http = require('http');

http.createServer(function (req, res) {
  //开启cors
  res.writeHead(200,{
    //允许的源
    'Access-Control-Allow-Origin': '*'//'http://localhost:5500'
  })
  res.end('Hello world');
}).listen(3000);

CORS(Cross-Origin Resource Sharing)是另一种更为强大和灵活的跨域解决方案。它通过在HTTP响应头中添加特定的CORS头信息(如 Access-Control-Allow-Origin),其中的*代表允许任何形式的源访问资源。CORS支持各种HTTP方法和数据类型,可以根据具体的场景设置条件允许其访问。

WebSocket

<script>
    function web_Socket(url,params={}) {
      return new Promise((resolve) => {
        const socket = new WebSocket(url)
        socket.onopen = () => {
          socket.send(JSON.stringify(params))
        }
        socket.onmessage = (res) => {
          resolve(res.data)
        }
      })
    }

    web_Socket('http://localhost:3000').then(data => {
      console.log(data);
    })
  </script>

WebSocket是一种全双工通信协议,它提供了一个持久连接,可以在客户端和服务端之间进行双向数据传输。在通讯连接成功后onopen会将params对象转换为字符串的形式传递给后端,当WebSocket接收到消息时onmessage触发,这里它将接收到的消息数据解析并调用 resolve 函数来解析Promise

const WebSocket = require('ws');
const ws = new WebSocket.Server({ port: 3000 });
let count=1;
ws.on('connection',(obj)=>{
    obj.on('message',(data)=>{
        // obj.send('this is a message');
        setInterval(()=>{
            count++
            obj.send(count)
        },2000)
    })
})

image.png

在双向通信连接成功后,服务器有新的信息就会向前端推送。

WebSocket协议本身不受同源策略的限制,非常适合需要实时双向通信的应用场景。不过,它的实现相对复杂,且需要服务器端的支持。

postMessage

HTML5引入了 postMessage API,使得不同源的窗口对象之间可以进行消息传递。这对于父窗口与子窗口(如iframe)之间的通信非常有用。尽管 postMessage 提供了基本的跨域通信能力,但由于其功能较为有限,可能不适用于所有场景。

document.domain

对于拥有相同顶级域名的不同子域,可以通过设置 document.domain 来实现跨子域通信。虽然这种方法可以简化某些情况下的跨域问题,但它也有明显的局限性,例如不能跨越不同的顶级域名,并且在HTTPS环境下可能受到限制。

Nginx代理

在服务器端使用Nginx作为反向代理,通过配置Nginx来转发请求并处理响应。这种方式可以绕过浏览器的同源策略限制,特别适合于那些后端有权限修改服务器配置的情况。通过Nginx代理,可以实现更高级别的控制,并且有助于提高系统的整体性能。

结论

综上所述,跨域问题是Web开发中不可避免的一个挑战。不同的解决方案各有千秋,选择最合适的方案取决于具体的应用场景和技术栈。无论是使用简单的JSONP,还是复杂的WebSocket,或者是介于两者之间的CORS,开发者都应该综合考虑其适用性和安全性,以确保既能满足业务需求又能保障系统的安全性。