一、前端解决跨域
前端解决跨域问题有多种成熟方案,具体选择取决于你的开发环境、项目需求以及对安全性和兼容性的要求。下面这个表格汇总了主要的解决方法,帮你快速了解每种方案的核心原理和适用场景。
方法名称 | 核心原理 | 适用场景 | 前端工作量 |
---|---|---|---|
CORS (跨域资源共享) | 服务器设置特定的HTTP响应头(如 Access-Control-Allow-Origin ),告知浏览器允许跨域请求。 |
前后端分离项目,后端可配合设置。生产环境的首选方案。 | 几乎为零,主要在服务端配置。 |
开发服务器代理 (Proxy) | 利用前端开发服务器(如webpack-dev-server, Vite)将API请求代理到同源的后端服务,绕过浏览器同源策略。 | 前端开发环境最常用、最方便的解决方案。 | 只需修改项目配置文件。 |
JSONP | 利用 <script> 标签天然可跨域的特性,通过动态创建标签来获取数据,需服务器返回函数调用包装的数据。 |
仅支持GET请求,通常用于向老旧接口获取数据,现代开发中已较少使用。 | 需要编写特定回调逻辑。 |
Nginx反向代理 | 类似开发服务器代理,但由Nginx服务器完成请求转发。部署阶段常用。 | 生产环境部署,尤其是静态资源和服务由同一域名提供时。 | 需部署和配置Nginx服务器。 |
WebSocket | WebSocket协议本身不受同源策略限制。 | 需要双向实时通信的应用(如聊天室、在线游戏)。 | 需改造为WebSocket通信模式。 |
postMessage | HTML5 API,允许不同源的窗口/iframe间安全地进行消息传递-3-7。 | 与嵌入的第三方页面或不同子域页面进行数据通信。 | 需在发送和接收方编写消息监听与发送代码。 |
🔧 开发环境首选:代理服务器
在开发阶段,你最方便的选择是使用构建工具自带的代理功能。这能让你像访问同源资源一样访问后端API,从而避免跨域问题。
以 Vue + Webpack 项目为例 ,在 vue.config.js
文件中进行配置:
java
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': { // 代理所有以 '/api' 开头的请求
target: 'http://your-backend-server.com', // 后端服务器地址
changeOrigin: true, // 修改请求头中的Origin为目标地址,确保后端能正确识别:cite[6]
pathRewrite: {
'^/api': '' // 重写路径,去掉请求路径中的 '/api' 前缀:cite[6]
}
}
}
}
}
配置后,你前端的请求 /api/users
会被代理到 http://your-backend-server.com/users
。
Vite 项目 的配置也很类似,在 vite.config.js
中设置:
javascript
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^/api/, ''),
},
},
},
}
生产环境标准:CORS
CORS是W3C标准,也是生产环境下最规范、安全的跨域解决方案。它依赖于后端服务器设置特定的HTTP响应头。
常见的CORS响应头包括:
Access-Control-Allow-Origin
: 指定允许访问资源的源。*
表示允许任何源,但若请求需要携带凭证(如cookies),则不能使用*
,必须指定明确的域名。Access-Control-Allow-Methods
: 指定允许的HTTP方法,如GET, POST, PUT, DELETE
。Access-Control-Allow-Headers
: 指定允许的请求头,如Content-Type, Authorization
。Access-Control-Allow-Credentials
: 设置为true
时,允许请求携带凭证信息。
Node.js (Express框架) 配置CORS示例:
javascript
const express = require('express');
const app = express();
// 使用CORS中间件
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://your-frontend-domain.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'); // 如果需要携带cookie
next();
});
app.listen(3000);
传统方法:JSONP
JSONP是一种传统方法,主要用于GET请求。其原理是动态创建<script>
标签,通过src
属性发起请求,并指定一个回调函数名,服务器返回的数据会被该回调函数包装。
前端实现示例:
ini
function handleJSONP(data) {
// 处理返回的数据
console.log(data);
}
const script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleJSONP'; // 将回调函数名传递给服务器
document.head.appendChild(script);
服务器需要返回 handleJSONP({"key": "value"})
格式的数据。需要注意的是,JSONP只支持GET请求,并且存在一定的安全风险(如XSS攻击)。
如何选择?
- 开发阶段 :优先使用 开发服务器代理,配置简单,能极大提升开发效率。
- 生产环境 :与后端协作,规范配置 CORS,这是最安全、标准的方式。
- 特殊场景 :如需与嵌入的iframe通信,考虑
postMessage
;需要实时双向通信,考虑WebSocket。
二、后端解决跨域
后端解决跨域是最推荐、最安全的方式,主要通过配置服务器来允许跨域请求。以下是各种后端框架的详细解决方案:
1. Spring Boot 解决方案
方案一:使用 @CrossOrigin
注解(方法级/类级)
less
// 在控制器类上使用 - 该类所有接口支持跨域
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api")
public class UserController {
// 或者在具体方法上使用
@CrossOrigin(origins = "http://localhost:3000")
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
// 更详细的配置
@CrossOrigin(
origins = {"http://localhost:3000", "https://example.com"},
methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT},
allowedHeaders = "*",
allowCredentials = "true",
maxAge = 3600
)
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
方案二:全局配置类(推荐使用)
typescript
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 所有接口路径
//.allowedOrigins("*") // 允许所有域名(Spring Boot 2.4+ 已弃用)
.allowedOriginPatterns("*") // 使用模式匹配(推荐)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许方法
.allowedHeaders("*") // 允许所有头
.allowCredentials(true) // 允许凭证
.maxAge(3600); // 预检请求缓存时间
}
}
方案三:使用 Filter 过滤器
arduino
@Configuration
public class CorsFilterConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 设置允许的源
config.addAllowedOriginPattern("*");
// 或者指定具体域名
// config.addAllowedOrigin("http://localhost:3000");
// config.addAllowedOrigin("https://example.com");
// 设置允许的方法
config.addAllowedMethod("*");
// 或者指定具体方法
// config.addAllowedMethod("GET");
// config.addAllowedMethod("POST");
// config.addAllowedMethod("PUT");
// 设置允许的头部
config.addAllowedHeader("*");
// 是否允许凭证
config.setAllowCredentials(true);
// 预检请求的有效期,单位秒
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
方案四:手动处理 OPTIONS 预检请求
java
@Component
public class SimpleCorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
// 设置允许的源
response.setHeader("Access-Control-Allow-Origin", "*");
// 或者指定具体域名
// response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
// 设置允许的方法
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
// 设置允许的头部
response.setHeader("Access-Control-Allow-Headers",
"Content-Type, Authorization, Content-Length, X-Requested-With");
// 允许携带凭证
response.setHeader("Access-Control-Allow-Credentials", "true");
// 处理OPTIONS预检请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return;
}
chain.doFilter(req, res);
}
}
2. 生产环境安全配置
安全的最佳实践配置
typescript
@Configuration
public class ProductionCorsConfig implements WebMvcConfigurer {
@Value("${cors.allowed.origins:}")
private String[] allowedOrigins;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 只对API接口生效
.allowedOrigins(allowedOrigins) // 从配置文件中读取允许的域名
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Content-Type", "Authorization", "X-Requested-With")
.allowCredentials(true)
.maxAge(1800); // 30分钟
// 可以对不同的路径设置不同的规则
registry.addMapping("/public/**")
.allowedOriginPatterns("*")
.allowedMethods("GET")
.allowCredentials(false);
}
}
配置文件(application.yml)
yaml
# 生产环境配置
cors:
allowed:
origins:
- "https://www.example.com"
- "https://api.example.com"
# 开发环境配置
---
spring:
profiles: dev
cors:
allowed:
origins:
- "http://localhost:3000"
- "http://127.0.0.1:3000"
- "http://localhost:8080"
3. 其他后端框架解决方案
Node.js (Express)
php
const express = require('express');
const cors = require('cors');
const app = express();
// 简单的CORS配置
app.use(cors());
// 详细的CORS配置
app.use(cors({
origin: ['http://localhost:3000', 'https://example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 3600
}));
// 或者手动设置响应头
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
});
Python (Django)
ini
# settings.py
INSTALLED_APPS = [
# ...
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # 尽量放在最前面
# ...
]
# CORS配置
CORS_ALLOW_ALL_ORIGINS = True # 开发环境使用
# 生产环境配置
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"https://example.com",
]
CORS_ALLOW_METHODS = [
'GET',
'POST',
'PUT',
'DELETE',
]
CORS_ALLOW_HEADERS = [
'content-type',
'authorization',
]
CORS_ALLOW_CREDENTIALS = True
PHP
php
<?php
header("Access-Control-Allow-Origin: http://localhost:3000");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Max-Age: 3600");
// 处理OPTIONS预检请求
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
http_response_code(200);
exit();
}
// 你的业务逻辑...
?>
4. Nginx 反向代理解决方案
ini
server {
listen 80;
server_name api.example.com;
# 跨域配置
location /api/ {
# 设置跨域头
add_header Access-Control-Allow-Origin 'http://localhost:3000' always;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Content-Type, Authorization' always;
add_header Access-Control-Allow-Credentials 'true' always;
add_header Access-Control-Max-Age 3600 always;
# 处理OPTIONS请求
if ($request_method = 'OPTIONS') {
return 204;
}
# 代理到实际的后端服务
proxy_pass http://backend-server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
5. 常见问题解决方案
问题1:携带 Cookie 时的跨域问题
typescript
// 当需要携带Cookie时,不能使用通配符 *
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
// .allowedOrigins("*") // 错误!与allowCredentials(true)冲突
.allowedOriginPatterns("*") // 或者指定具体域名
.allowCredentials(true) // 允许携带凭证
// ... 其他配置
}
}
问题2:自定义头部字段
arduino
// 如果前端使用了自定义头部字段
registry.addMapping("/**")
.allowedHeaders("Content-Type", "Authorization", "X-Custom-Header")
.exposedHeaders("X-Custom-Header"); // 允许前端访问的自定义响应头
6. 最佳实践总结
- 开发环境:使用宽松配置,方便调试
- 生产环境:严格限制允许的域名和方法
- 推荐使用全局配置:统一管理,避免遗漏
- 安全考虑 :不要随意使用
allowedOrigins("*")
- 性能优化 :合理设置
maxAge
减少预检请求
Spring Boot 项目最推荐使用方案二(全局配置类) ,这种方式既灵活又易于维护,能够满足大多数项目的跨域需求。