前端解决跨域vs面试官,你们知道吗?

570 阅读7分钟

跨域问题详解与解决方案

在前端开发中,跨域是一个常见的问题。无论是前后端分离的架构还是微服务的设计,跨域问题都可能成为开发中的拦路虎。本文将详细介绍跨域的成因、常见解决方法以及如何通过合理配置避免潜在的安全隐患。


什么是跨域?

跨域是指浏览器出于安全考虑,限制了从一个源加载的文档或脚本发起对另一个源的请求。这种限制被称为“同源策略”(Same-Origin Policy)。
同源指的是协议、域名和端口号完全一致。例如:

  • http://example.com 和 https://example.com 不同源(协议不同)。
  • http://example.com 和 http://api.example.com 不同源(域名不同)。
  • http://example.com:8080 和 http://example.com:3000 不同源(端口不同)。

当页面上的 JavaScript 尝试向非同源的服务器发送请求时,浏览器会阻止该请求,从而导致跨域问题。


跨域的解决方法

1. CORS (Cross-Origin Resource Sharing)

原理

CORS 是一种基于 HTTP 头部的跨域解决方案。服务器通过设置响应头中的 Access-Control-Allow-Origin 字段,允许特定的域名访问资源。

实现步骤
  1. 客户端发起跨域请求。
  2. 浏览器自动在请求头中添加 Origin 字段,表示请求来源。
  3. 服务器接收到请求后,检查 Origin 是否在允许的列表中。
  4. 如果允许,则在响应头中添加 Access-Control-Allow-Origin 字段,并返回数据。
示例代码

// 客户端
fetch('https://api.example.com/data', {
    method: 'GET',
    headers: {
        'Content-Type': 'application/json'
    }
})
.then(response => response.json())
.then(data => console.log(data));

// 服务器端(Node.js + Express)
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', 'https://yourdomain.com');
    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    next();
});
优点
  • 支持所有类型的 HTTP 请求。
  • 安全性高,符合 W3C 标准。
缺点
  • 需要服务器端进行配置。
  • 对老旧服务器支持有限。

2. JSONP

原理

JSONP(JSON with Padding)通过动态创建 <script> 标签绕过浏览器的同源策略。由于 <script> 标签不受同源限制,可以加载任意域名的 JavaScript 文件。

实现步骤

1.客户端提前创建好回调函数,用于处理服务器返回的数据

2.动态创建一个script标签,将请求的url作为src属性的值,并在url中添加回调函数的名称作为参数,将script标签加到body中,document.body.appendChild(script),让浏览器才会真正发起对

3.服务器接收到请求后,先提取参数里的回调参数名,返回一段js代码,内容是调用客户端定义的回调函数,并将数据作为参数传入。

4.浏览器加载并执行

示例代码
// 客户端
<!-- // 1.客户端提前创建好回调函数,用于处理服务器返回的数据
// 2.动态创建一个script标签,将请求的url作为src属性的值,并在url中添加回调函数的名称作为参数,将script标签加到body中,
// document.body.appendChild(script),让浏览器才会真正发起对 <script> 元素 src 属性中指定的 URL 的请求。
// 3.服务器接收到请求后,先提取参数里的回调参数名,返回一段js代码,内容是调用客户端定义的回调函数,并将数据作为参数传入。
// 4.浏览器加载并执行 <script> 标签中的代码,从而调用客户端的回调函数。 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 1.客户端提前创建好回调函数,用于处理服务器返回的数据
        function handleData(data) {
            console.log(data);
        }
        var script = document.createElement('script');
        script.src = 'http://example.com/api?callback=handleData';
        document.body.appendChild(script);

    </script>
</body>

</html>

// 服务器端
const express = require('express');
const app = express();
const port = 3000;

// 定义一个处理 JSONP 请求的路由
app.get('/api', (req, res) => {
    // 获取回调函数名,默认使用 'callback' 作为参数名
    const callback = req.query.callback;
    // 模拟要返回的数据
    const data = {
        message: 'Hello from Node.js JSONP server!',
        status: 200
    };

    if (callback) {
        // 将数据包装在回调函数中
        const jsonpResponse = `${callback}(${JSON.stringify(data)})`;
        // 设置响应头
        res.setHeader('Content-Type', 'application/javascript');
        // 发送 JSONP 响应
        res.send(jsonpResponse);
    } else {
        // 如果没有提供回调函数名,发送普通的 JSON 响应
        res.json(data);
    }
});

app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});
优点
  • 兼容性好,适用于大多数浏览器。
缺点
  • 只支持 GET 请求。
  • 安全性较低,容易受到 XSS 攻击。

3. 代理服务器

原理

通过在同源的服务器上设置代理,转发前端请求到目标服务器,并将目标服务器的响应返回给前端。由于前端和代理服务器是同源的,因此不会受到浏览器的同源策略限制。

实现步骤
  1. 在前端项目中配置代理服务器。
  2. 所有跨域请求通过代理服务器转发。
示例代码
// Vue CLI 配置文件 vue.config.js
module.exports = {
    devServer: {
        proxy: {
            '/api': {
                target: 'https://api.example.com',
                changeOrigin: true,
                pathRewrite: { '^/api': '' }
            }
        }
    }
};
优点
  • 不需要目标服务器进行任何配置。
  • 适用于各种场景。
缺点
  • 增加了服务器的负担。
  • 需要额外的服务器资源来运行代理服务器。

4. WebSocket

原理

WebSocket 是一种全双工通信协议,可以在单个 TCP 连接上进行双向数据传输。由于 WebSocket 不受同源策略的限制,因此可以用来解决跨域问题。

实现步骤
  1. 客户端通过 WebSocket 协议连接到服务器。
  2. 服务器验证客户端来源后建立连接。
  3. 双方可以通过连接实时传输数据。
示例代码
javascript
深色版本
// 客户端
const socket = new WebSocket('wss://api.example.com/socket');
socket.onmessage = function(event) {
    console.log('收到消息:', event.data);
};

// 服务器端(Node.js + ws 库)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws) {
    ws.on('message', function incoming(message) {
        console.log('收到消息:', message);
    });
});
优点
  • 支持实时通信。
  • 性能高,不受同源策略限制。
缺点
  • 需要服务器端支持 WebSocket 协议。
  • 对老旧服务器支持有限。

5. iframe 和 postMessage

原理

通过 window.postMessage() 方法在不同的窗口之间(如 iframe、弹出窗口等)进行跨域通信。

实现步骤
  1. 父页面通过 iframe.contentWindow.postMessage() 向子页面发送消息。
  2. 子页面通过监听 message 事件接收消息。
示例代码
javascript
深色版本
// 父页面
const iframe = document.getElementById('myIframe');
iframe.contentWindow.postMessage('Hello from parent!', 'https://child.example.com');

// 子页面
window.addEventListener('message', function(event) {
    if (event.origin !== 'https://parent.example.com') return;
    console.log('收到消息:', event.data);
});
优点
  • 简单易用,适合简单的跨域通信需求。
缺点
  • 仅适用于窗口之间的通信。
  • 需要双方配合实现。

如何加入白名单?

通过设置 HTTP 响应头中的 Access-Control-Allow-Origin 字段,可以指定哪些域名可以访问资源。例如:

javascript
深色版本
res.setHeader('Access-Control-Allow-Origin', 'https://yourdomain.com');

如果需要允许所有域名访问,可以设置为通配符 *,但需要注意安全性问题。


CSP (Content Security Policy)

CSP 是一种防止 XSS 攻击的安全机制,通过定义允许加载的资源来源来限制潜在的风险。可以通过以下方式配置 CSP:

// 使用 helmet 设置 CSP
app.use(
    helmet.contentSecurityPolicy({
        directives: {
            defaultSrc: ["'self'"],
            scriptSrc: ["'self'", 'https://trusted-script.com'],
            styleSrc: ["'self'", "'unsafe-inline'"],
            imgSrc: ["'self'", 'https://trusted-image.com'],
            connectSrc: ["'self'", 'https://api.example.com']
        }
    })
);

helmet 库来设置内容安全策略,简称 CSP,这段代码使用 helmet 中间件为 Express 应用程序设置了内容安全策略。helmet 是一个用于帮助设置各种 HTTP 头的 Node.js 库,以增强应用程序的安全性。helmet.contentSecurityPolicy 方法用于设置 CSP 相关的 HTTP 头。


总结

跨域问题虽然复杂,但通过合理选择解决方案,可以有效应对。以下是推荐的使用场景:

  • CORS:适用于大多数跨域场景,安全性高。
  • JSONP:适用于只支持 GET 请求的简单场景。
  • 代理服务器:适用于无法修改目标服务器配置的场景。
  • WebSocket:适用于需要实时通信的场景。
  • iframe 和 postMessage:适用于窗口之间的跨域通信。

希望本文能帮助你更好地理解和解决跨域问题!如果你有任何疑问或建议,欢迎在评论区留言交流~