面试必问的八股:如何解决跨域问题

前言

这两个月,笔者可以说是被面试折磨的苦不堪言,尽管中小厂offer拿了不知道多少,但冲击大厂又是屡屡受挫😭,笔者发现不管是大厂还是中小厂基本都会问跨域问题是什么,怎么解决的,有几种解决方法。接下来就跟笔者一起来看看吧。以后面试碰到了就可以直接吟唱了(。・∀・)ノ

同源策略

跨域问题其实就是浏览器的同源策略造成的,设置同源策略主要是为了保护用户信息的安全,防止恶意网站通过脚本访问和操作其他网站上的敏感数据。它只是对js脚本的一种限制,并不是对浏览器的限制,对于一般的img或者script脚本请求都不会有跨域的限制。这里的"源"指的是协议、域名和端口的组合。只有当两个URL的协议、域名和端口都相同时,它们才是同源,只要其中任何一项不同,就被认为是不同源。

如何解决跨域问题

jsonp

原理

jsonp的原理是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端讲接口返回数据拼凑导callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

缺点

  • jsonp不安全,容易遭到XSS(注入恶意脚本)攻击
  • 只支持GET方法,因为script中的src不能发送POST等其他请求,所以会阻塞页面的渲染,影响性能。
  • 不太好处理HTTP错误,回调函数只会在成功时调用。
js 复制代码
<script>
        // 定义回调函数
        function handleCallback(res) {
            alert(JSON.stringify(res));
        }

        // 创建 script 标签
        var script = document.createElement('script');
        script.type = 'text/javascript';

        // 设置 script 的 src 属性,包含 URL 和回调函数名称
        script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';

        // 将 script 标签添加到页面中
        document.head.appendChild(script);
    </script>

cors

MDN中关于cors的介绍如下: CORS(跨域资源共享)是一种机制,它使用额外的HTTP头来告诉浏览器,让运行在一个origin上的web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域HTTP请求

cors需要服务器和浏览器同时支持,整个cors过程都是浏览器完成的,无需用户参与,因此实现cors的关键就是服务器,只要服务器实现了cors请求,就可以实现跨域了。

CORS 的核心 HTTP 头

  • Access-Control-Allow-Origin:指定允许访问资源的源(域名)。
  • Access-Control-Allow-Methods:指定允许的 HTTP 方法(如 GET、POST、PUT 等)。
  • Access-Control-Allow-Headers :指定允许的请求头(如 Content-TypeAuthorization 等)。
  • Access-Control-Allow-Credentials:是否允许携带凭据(如 Cookies)。
  • Access-Control-Max-Age:预检请求的缓存时间(单位:秒)。

预检请求(Preflight Request)

对于复杂请求(如 PUT、DELETE 等),浏览器会先发送一个 OPTIONS 请求,询问服务器是否允许该跨域请求。服务器通过响应头返回允许的配置。

示例代码:

javascript:d:\HuaweiMoveData\Users\86158\Desktop\workspace\lesson_ai\interview\chunzhao\js\cors\preflight-demo\index.js 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
  const headers = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Custom-Header',
    'Access-Control-Max-Age': '86400',
  };

  if (req.method === 'OPTIONS') {
    // 处理预检请求
    res.writeHead(204, headers);
    console.log('预检请求');
    res.end();
  }

  if (req.method === 'PUT' && req.url === '/data') {
    res.writeHead(201, headers);
    res.end('PUT 请求成功');
  } else {
    res.writeHead(404, headers);
    res.end('404 Not Found');
  }
});

server.listen(3000, () => {
  console.log('服务器已启动,监听端口 3000');
});

复杂请求

在客户端,可以通过 XMLHttpRequestfetch 发送复杂请求。浏览器会自动处理预检请求。

示例代码:

html:d:\HuaweiMoveData\Users\86158\Desktop\workspace\lesson_ai\interview\chunzhao\js\cors\preflight-demo\index.html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CORS Preflight Example</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest();
xhr.open('PUT', 'http://localhost:3000/data', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({ key: 'value' }));

xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if (xhr.status >= 200 && xhr.status < 300) {
      console.log(xhr.status, 'CORS Preflight successful');
    } else {
      console.error('CORS Preflight failed');
    }
  }
};
</script>
</body>
</html>

注意事项

  • 安全性 :避免使用 Access-Control-Allow-Origin: *,应明确指定允许的域名。
  • 缓存优化 :通过 Access-Control-Max-Age 减少预检请求的频率。
  • 凭据处理 :如果需要携带 Cookies,需设置 Access-Control-Allow-Credentials: true

websocket协议跨域

WebSocket是一种基于TCP的双向通信协议,允许客户端和服务器之间建立持久连接,实现全双工实时数据传输。与HTTP不同,WebSocket在握手阶段通过HTTP协议完成,之后切换到独立的通信协议,不再受同源策略限制。

请看以下代码:

index.html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebSocket Cross-Origin</title>
</head>
<body>
  <script>
    // html5 websocket 新特性 双向通信
    // http://   ws://  
    // websocket 不受同源策略的影响
    const ws = new WebSocket('ws://localhost:8080/ws'); 

    ws.onopen = function(){
      console.log('Connection opened')
      ws.send('Hello from client')
    }

    ws.onmessage = (event)=>{
      console.log(`message from server:${event.data}`)
    }
  </script>
</body>
</html>
server.js 复制代码
const WebSocket = require('ws'); 
const http = require('http'); 

const server = http.createServer((req,res)=>{
  res.writeHead(200,{
    'Content-type':'text/plain'
  })
  res.end('WebSocket Server')
})
// web + socket
const wss = new WebSocket.Server({
  server,
  path:'/ws'
})

wss.on('connection',(ws)=>{
  console.log('Client connected')
  // 双向通信
  ws.on('message',(message)=>{
    console.log(`Received message: ${message}`)
    ws.send(`Server received: ${message}`)
  })
  ws.send('Welcome to WebSocket Server')
})

server.listen(8080,()=>{
  console.log('WebSocket Server is running on port 8080');
})
// 1. 创建服务器 http
// 2. 创建 WebSocket 服务器
// 3. 建立一次http 连接

postMessage

在现代Web开发中,不同窗口或iframe之间的通信是一个常见的需求。HTML5引入了postMessage接口,提供了一种安全、灵活的方式来实现跨窗口(包括跨域)的通信。

什么是postMessage?

postMessage是HTML5新增的功能,允许一个窗口(如父窗口)向另一个窗口(如iframe)发送消息,即使它们来自不同的源(即跨域)。这也是为数不多可以跨域操作的window属性之一,通过postMessage,开发者可以在保证安全性的同时,实现窗口间的双向通信。

基本用法

调用window.postMessage方法可以向目标窗口发送消息。该方法接受两个参数:

  • message:要发送的数据,可以是字符串、对象或其他可序列化的数据。
  • targetOrigin :目标窗口的URL,用于验证消息的安全性。如果不确定目标窗口的具体地址,可以使用*表示任意源。

示例(父窗口向iframe发送消息):

dart 复制代码
javascript
const iframe = document.querySelector('.child-iframe');
iframe.contentWindow.postMessage('来自父窗口的消息', '*');

目标窗口可以通过监听message事件来接收消息。事件对象e包含以下属性:

  • e.data:接收到的消息内容。
  • e.origin:消息来源的URL。
  • e.source:发送消息的窗口对象。

示例(iframe接收父窗口的消息):

javascript 复制代码
javascript
window.addEventListener('message', (e) => {
  console.log('收到父窗口的消息:', e.data);
  // 可以通过e.origin验证消息来源
  if (e.origin === 'http://example.com') {
    console.log('消息来自可信源');
  }
});

完整代码如下:

index.html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Parent Window</title>
</head>
<body>
  <h1>This is parent window</h1>
  <input type="text" class="inp" placeholder="输入消息">
  <button class="send">发送信息到iframe</button>
  <iframe src="child.html" height="600" width="800" class="child-iframe" frameborder="0"></iframe>
  <script>
    const iframe = document.querySelector('.child-iframe');
    document.querySelector('.send').addEventListener('click', () => {
      const message = document.querySelector('.inp').value;
      iframe.contentWindow.postMessage(message, '*');
    });

    // 监听来自iframe的消息
    window.addEventListener('message', (e) => {
      console.log('收到iframe的消息:', e.data);
    });
  </script>
</body>
</html>
child.html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Iframe Child</title>
</head>
<body>
  <h1>This is iframe child page.</h1>
  <script>
    // 监听来自父窗口的消息
    window.addEventListener('message', (e) => {
      console.log('收到父窗口的消息:', e.data);
      // 向父窗口发送回复
      window.parent.postMessage('来自子窗口的回复', '*');
    });
  </script>
</body>
</html>

优势

postMessage提供了targetOrigin参数,用于限制消息只能发送到指定的源。这可以有效防止消息被发送到恶意网站,它也支持传递多种类型的数据,包括字符串、对象、数组等。此外,它还可以用于跨域通信,解决了传统方法(如JSONP)的局限性。

注意事项

  1. 验证消息来源 :始终检查e.origin,确保消息来自可信源。
  2. 避免敏感数据泄露 :不要通过postMessage传递敏感信息,除非完全信任目标窗口。
  3. 兼容性postMessage已被所有现代浏览器支持,但在老旧浏览器中可能需要降级处理。

vite proxy

vite 提供了一种内置的反向代理方式, 反向代理是一种服务器端技术,客户端的请求会先发送到代理服务器,代理服务器再将请求转发到目标服务器,并将响应返回给客户端。通过反向代理,可以隐藏后端服务的真实地址,同时解决跨域问题。

Vite 中的 Proxy 配置

Vite 使用 vite.config.js 文件中的 server.proxy 属性来配置反向代理。以下是基本用法:

javascript 复制代码
javascript
// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    proxy: {
      // 匹配以 /api 开头的路径
      '/api': {
        target: 'http://example.com', // 目标服务器地址
        changeOrigin: true,          // 是否改变请求源头,默认为true
        rewrite: (path) => path.replace(/^/api/, '') // 路径重写
      }
    }
  }
});
  • target:目标服务器的地址。
  • changeOrigin :是否更改请求的源头。设置为true时,代理服务器会修改请求头中的Host字段为目标服务器的地址。
  • rewrite :用于重写请求路径。例如,将/api前缀去掉。

工作原理

当开发服务器接收到一个匹配的请求(如/api/data),Vite 会将其转发到配置的目标服务器(如http://example.com/api/data)。目标服务器处理完请求后,将结果返回给开发服务器,最终返回给客户端。

nginx代理跨域

Nginx 是一个高性能的HTTP和反向代理服务器,常用于负载均衡、静态资源服务和反向代理,通过配置Nginx 的反向代理可以有效的解决前端开发中的跨域问题,其本质和cors跨域原理一样,通过配置文件设置请求响应头Acess-Control-Allow-Origin...等字段

相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端