前端跨域问题

265 阅读4分钟

前言

跨域问题

跨域请求描述

前端跨域,指的是在浏览器端,通过 XMLHttpRequest 或 Fetch API 等方式向不同域名、不同端口或不同协议的资源发起请求时,会受到同源策略(Same-Origin Policy)的限制,导致请求失败。

解决方案

1、JSONP

描述

利用 script 标签的跨域特性,将数据包装成 JavaScript 函数的调用,从而实现跨域请求。但该方法只支持 GET 请求,且容易受到 XSS 攻击

代码示例(react)

使用 React 的函数组件来实现,使用了 useState 和 useEffect 两个 Hook。在 useEffect 中,我们动态创建一个  作为参数添加到 URL 中,从而实现跨域请求。在回调函数 handleResponse 中,我们将返回的数据保存到组件的状态中,从而触发组件的重新渲染。

需要注意的是,由于 JSONP 跨域请求是通过动态创建 script 标签来实现的,因此我们需要在组件卸载时手动移除该标签,以避免内存泄漏。在本例中,我们通过返回一个清理函数来实现该功能

import React, { useState, useEffect } from 'react';

function JsonpExample() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'http://example.com/data?callback=handleResponse';
    document.body.appendChild(script);
    return () => {
      document.body.removeChild(script);
    }
  }, []);

  function handleResponse(response) {
    setData(response);
  }

  return (
    <div>
      {data ? (
        <p>Hello, {data.name}!</p>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
}

export default JsonpExample;

2、CORS

描述

通过在服务器端设置响应头,允许指定的域名访问资源。需要服务器端的支持,且需要浏览器支持 CORS。credentials 属性为 'include',表示允许跨域请求携带 Cookie 或认证信息;同时设置了 Origin 请求头,表示请求的源地址

代码示例(react)

import React, { useState, useEffect } from 'react';

function CorsExample() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('http://example.com/api', {
      method: 'GET',
      credentials: 'include',
      headers: {
        'Origin': 'http://localhost:3000'
      }
    })
    .then(response => response.json())
    .then(response => setData(response))
    .catch(error => console.error(error));
  }, []);

  return (
    <div>
      {data ? (
        <p>Hello, {data.name}!</p>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
}

export default CorsExample;

3、代理

描述

在同一域名下,前端发送请求到自己的服务器,再由服务器转发请求到另一个域名,最后将响应结果返回给前端

代码示例(react)

import React, { useState, useEffect } from "react";

function App() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch("/api/data")
      .then((res) => res.json())
      .then((result) => {
        setData(result);
      })
      .catch((error) => console.log(error));
  }, []);

  return (
    <div>
      {data ? (
        <ul>
          {data.map((item, index) => (
            <li key={index}>{item.title}</li>
          ))}
        </ul>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
}

export default App;

转发原理示例代码

在这个文件中,我们使用 createProxyMiddleware 函数来创建一个代理,将 /api 路径下的请求转发到 http://localhost:3000changeOrigin 选项设置为 true,以确保代理服务器设置正确的 Host 头部。

这样,我们就可以在 React 组件中使用 fetch('/api/data') 来获取数据,代理服务器会将请求转发到后端服务器,并返回响应,我们的 React 组件就可以正常处理响应数据了。

    const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:3000',
      changeOrigin: true,
    })
  );
};

框架集成代理Proxy 示例代码

前端框架中集成的代理Proxy,通常是基于ES6中新增的Proxy对象。通过创建一个Proxy对象,我们可以定义一个目标对象(被代理对象),并在目标对象上定义一组拦截器(handler),这些拦截器会拦截对目标对象的(类似重定向)各种操作。

    const target = { name: "Alice", age: 30 };
const handler = {
  get: function(target, prop) {
    console.log(`Reading property "${prop}"`);
    return target[prop];
  },
  set: function(target, prop, value) {
    console.log(`Setting property "${prop}" to "${value}"`);
    target[prop] = value;
  }
};
const proxy = new Proxy(target, handler);

console.log(proxy.name); // Reading property "name"; "Alice"
proxy.age = 31; // Setting property "age" to "31"

4、postMessage

描述

使用 HTML5 中的 postMessage 方法,在不同的窗口之间传递数据。这种方式需要在目标页面中添加监听事件

代码示例(react)

在这个示例中,我们创建了一个简单的 React 应用,其中包含一个按钮,点击按钮后会向目标窗口发送一条消息。同时,在页面中还展示了接收到的消息内容。

在 sendMessage 函数中,我们获取了目标窗口的引用,并使用 postMessage 方法向目标窗口发送了一条消息。在 useEffect 中,我们使用 addEventListener 方法监听 message 事件,并在事件处理函数中根据消息的来源进行了验证,并将消息内容保存到组件状态中以便在页面上展示。

需要注意的是,在实际应用中,需要根据实际情况设置目标窗口的源,以确保消息只会被发送到指定的窗口中

import React, { useState, useEffect } from 'react';

const TargetOrigin = 'http://localhost:3000'; // 目标窗口的源

function App() {
  const [message, setMessage] = useState('');

  // 发送消息到目标窗口
  const sendMessage = () => {
    const targetWindow = window.opener || window.parent;
    if (targetWindow) {
      targetWindow.postMessage('Hello from React!', TargetOrigin);
    }
  };

  // 监听消息事件
  useEffect(() => {
    const receiveMessage = (event) => {
      if (event.origin === TargetOrigin) {
        setMessage(event.data);
      }
    };
    window.addEventListener('message', receiveMessage);
    return () => {
      window.removeEventListener('message', receiveMessage);
    };
  }, []);

  return (
    <div>
      <h1>React App</h1>
      <button onClick={sendMessage}>Send Message</button>
      <p>Received Message: {message}</p>
    </div>
  );
}

export default App;