前言
这两个月,笔者可以说是被面试折磨的苦不堪言,尽管中小厂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-Type
、Authorization
等)。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');
});
复杂请求
在客户端,可以通过 XMLHttpRequest
或 fetch
发送复杂请求。浏览器会自动处理预检请求。
示例代码:
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)的局限性。
注意事项
- 验证消息来源 :始终检查
e.origin
,确保消息来自可信源。 - 避免敏感数据泄露 :不要通过
postMessage
传递敏感信息,除非完全信任目标窗口。 - 兼容性 :
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...等字段