文章中英模式
布鲁斯前端面试题目 - 跨域是什么?怎么解决跨域问题?
深入解析跨来源资源共享(CORS)的工作原理、常见错误与解决方案。掌握前端面试中关于跨域的核心知识,包含各种解决跨域问题的方法。
文章中英模式
懒得看文章?那就来看视频吧
什么是跨域(CORS)?
跨来源资源共享(Cross-Origin Resource Sharing, CORS)是一种浏览器安全机制,用于控制不同来源(网域、协议或端口)之间的资源请求。简单来说,它就像是网站间的「边境管制」,决定哪些外部资源可以被访问。
举个例子:假设你正在浏览 https://shopping.com 的网站,而这个网站需要从 https://api.shopping.com 获取商品数据。虽然它们看起来很相似,但因为域名不同,浏览器会视为「跨域请求」,默认会阻止这种访问。
这就像你在一家商店(前端网站)想要取得另一家商店(API服务器)的商品,但需要对方明确同意才行。如果API服务器没有说「我允许shopping.com访问我的资源」,浏览器就会挡下这个请求。
⚠️ 同源的定义:
两个URL必须具有相同的协议(http/https)、域名和端口才被视为同源。就像住在同一栋大楼的邻居。
- 1. https://example.com 和 https://api.example.com 不同源(子域名不同,像是同一条街不同栋大楼)
- 2. http://example.com 和 https://example.com 不同源(协议不同,一个用走路一个用开车到同一地点)
- 3. https://example.com 和 https://example.com:8080 不同源(端口不同,像是同一栋大楼不同楼层)
CORS的工作机制
CORS有两种主要请求类型:简单请求和预检请求。
类型 | 特点 | 流程 | 使用场景 |
---|---|---|---|
简单请求 | • GET/HEAD/POST方法 • 基本头部 • 简单Content-Type | 直接发送请求,带Origin头部 | 简单表单提交、基础GET请求 |
预检请求 (主流用法) | • 特殊HTTP方法 • 自定义头部 • JSON等特殊Content-Type | 1. 先发OPTIONS请求询问 2. 服务器响应许可 3. 再发实际请求 | 现代API请求、JSON数据交换、带认证的请求 |
在现代Web开发中,预检请求是主流情况,因为大多数API请求都使用JSON格式和自定义头部,这些都会触发预检机制。
// 预检请求
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://example.com
Access-Control-Request-Method: POST // 即使是简单方法(POST)
Access-Control-Request-Headers: Content-Type, X-Custom-Header // 但有自定义头部触发预检
// 预检响应
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, X-Custom-Header
Access-Control-Max-Age: 86400 // 预检结果缓存时间(秒)
常见CORS错误
Access to fetch at 'https://api.example.com' from origin 'https://example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
最常见的CORS错误,表示服务器未设置Access-Control-Allow-Origin头部。
Access to fetch at 'https://api.example.com' from origin 'https://example.com' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
服务器未在Access-Control-Allow-Methods中允许请求的HTTP方法。
Access to fetch at 'https://api.example.com' from origin 'https://example.com' has been blocked by CORS policy: Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers in preflight response.
服务器未在Access-Control-Allow-Headers中允许请求的自定义头部。
解决跨域问题的方法
1. 后端配置CORS头部
最标准的解决方案是在后端服务器正确配置CORS头部:
// Node.js Express示例
const express = require('express');
const app = express();
// 配置CORS中间件
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://example.com'); // 或使用 * 允许所有来源
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Allow-Credentials', 'true'); // 允许携带凭证
// 处理预检请求
if (req.method === 'OPTIONS') {
return res.status(204).end();
}
next();
});
// 或使用cors套件
const cors = require('cors');
app.use(cors({
origin: 'https://example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
2. 使用代理服务器
在前端开发环境中,可以配置代理服务器转发请求:
// webpack开发服务器代理配置
module.exports = {
// ...
devServer: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
}
// Next.js代理配置
// next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'https://api.example.com/:path*',
},
]
},
}
3. 使用后端中继服务器
在Next.js 15中使用Route Handlers作为中继服务器转发请求:
这种方法有效是因为同源策略只适用于浏览器到服务器的请求,而服务器之间的请求不受CORS限制。
当我们的Next.js应用作为中继服务器时,浏览器只与同源的Next.js服务器通信,然后由Next.js服务器转发请求到第三方API并将响应返回给前端,从而绕过了CORS限制。
// app/api/proxy/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
try {
// 从第三方API获取数据
const response = await fetch(
`https://api.example.com/data?${searchParams}`
);
const data = await response.json();
// 返回数据给前端
return NextResponse.json(data);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch data' },
{ status: 500 }
);
}
}
4. 其他解决方案
- JSONP (仅适用于GET请求):利用script标签不受同源策略限制的特性,但只支持GET请求且安全性较低。
- 浏览器扩展:开发时可使用CORS浏览器扩展暂时禁用CORS,但仅适用于开发环境。
- 反向代理:使用Nginx、Apache等反向代理服务器转发请求。
CORS最佳实践
1. 安全性优先
- 避免使用 * 允许所有来源,明确指定允许的域名
- 只允许必要的HTTP方法和头部
- 谨慎使用Access-Control-Allow-Credentials
2. 性能优化
- 设置适当的Access-Control-Max-Age缓存预检结果
- 避免不必要的预检请求,使用简单请求格式
3. 开发便利性
- 开发环境可使用代理或浏览器扩展
- 生产环境必须正确配置CORS头部
🔥 常见面试题目
(一) 什么是CORS? 为什么浏览器需要同源政策?
解答:CORS是浏览器的安全机制,允许服务器声明哪些网站可以访问它的资源。同源政策就像网站间的防火墙,限制不同网站间的数据存取。
没有同源政策的危险:
恶意网站(evil.com) 用户的银行网站(bank.com)
| |
| 用户访问恶意网站 |
| 同时已登录银行网站 |
| |
|-- 尝试读取银行资料 --X |
| |
| 浏览器阻止请求 |
| (同源政策保护) |
如果没有同源政策,当你登录银行网站后,再访问恶意网站时,恶意网站可能会偷偷读取你的银行资料或执行转账等操作。CORS则是在需要跨域访问时,提供安全的标准机制。
(二) 解决CORS问题的最佳方法有哪些? 各有什么优缺点?
解答:解决CORS的主要方法:
方法 | 优点 | 缺点 |
---|---|---|
后端配置CORS标头 | 标准方案,配置灵活 | 需要后端配合 |
代理服务器 | 前端可独立解决 | 增加请求复杂度 |
后端中继服务器 | 完全控制请求处理 | 需维护额外服务 |
最理想的解决方案是后端正确配置CORS标头。如果无法修改API源,则考虑使用代理或中继服务器。
(三) 在前后端分离的开发环境中如何处理CORS问题?
解答:前后端分离开发环境中的CORS解决方案:
1. 开发环境代理:
// Vite (vite.config.js/ts)
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^/api/, '')
}
}
}
});
2. 后端开发环境配置:在开发环境允许localhost域名访问
3. 中间代理服务器:如果前端有使用Next.js,可以考虑使用Next.js的代理功能。
// app/api/proxy/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
try {
// 从第三方API获取数据
const response = await fetch(
`https://api.example.com/data?${searchParams}`
);
const data = await response.json();
// 返回数据给前端
return NextResponse.json(data);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch data' },
{ status: 500 }
);
}
}