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

前言

这两个月,笔者可以说是被面试折磨的苦不堪言,尽管中小厂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...等字段

相关推荐
KjPrime1 小时前
纯vue手写流程组件
前端·javascript·vue.js
码农不惑2 小时前
前端开发:Vue以及Vue的路由
前端·javascript·vue.js
lina_mua3 小时前
JavaScript 中的性能优化:从基础到高级技巧
开发语言·javascript·性能优化
烛阴4 小时前
JavaScript instanceof:你真的懂它吗?
前端·javascript
shadouqi5 小时前
1.angular介绍
前端·javascript·angular.js
痴心阿文6 小时前
React如何导入md5,把密码password进行md5加密
前端·javascript·react.js
hdk19936 小时前
Edge浏览器登录微软账户报错0x80190001的解决办法
前端·microsoft·edge
徐同保6 小时前
yarn 装包时 package里包含[email protected]报错
前端·javascript
群联云防护小杜6 小时前
分布式节点池:群联云防护抗DDoS的核心武器
前端·网络·分布式·udp·npm·node.js·ddos