跨域问题是由于浏览器的同源策略导致的,它阻止从一个源加载的文档或脚本与来自另一个源的资源进行交互。为了解决开发环境下的跨域问题,我们通常采用以下三种方式:
-
Webpack DevServer 的 proxy 配置
-
Vue CLI 的 devServer.proxy(实际上也是基于 Webpack DevServer)
-
后端配置 CORS
1. Webpack DevServer 的 proxy 配置
原理:
Webpack DevServer 在开发环境下启动一个本地开发服务器,它可以在服务器层面将特定的 API 请求转发到真正的后端服务器。由于跨域限制是浏览器的安全策略,服务器之间没有这个限制,所以通过代理绕过了跨域问题。
工作流程:
浏览器 (http://localhost:8080)
→ 请求 Webpack DevServer (/api/users)
→ Webpack DevServer 转发请求到真实后端 (http://api.example.com/users)
→ 后端响应返回给 DevServer → 浏览器
浏览器始终只与同源的 DevServer 通信,不知道后端的真实地址。
示例代码
webpack.config.js
const path = require('path');
module.exports = {
// ... 其他配置
devServer: {
port: 8080,
proxy: {
// 代理所有以 /api 开头的请求
'/api': {
target: 'http://api.example.com', // 后端服务器地址
changeOrigin: true, // 修改请求头中的 Origin 为目标地址
pathRewrite: {
'^/api': '' // 重写路径,去掉 /api 前缀
},
secure: false, // 如果代理 HTTPS 服务,需要设置为 false
logLevel: 'debug' // 查看代理日志
},
// 代理特定的多个路径
'/users': {
target: 'http://localhost:3000',
changeOrigin: true
},
'/products': {
target: 'http://localhost:3001',
changeOrigin: true
}
}
}
};
前端代码使用:
// 前端直接请求本地开发服务器,由代理转发到真实后端
fetch('/api/users')
.then(response => response.json())
.then(data => console.log(data));
// 实际请求会被转发到:http://api.example.com/users
2. Vue CLI (vue2) 的 devServer.proxy
原理
Vue CLI 底层也是使用 Webpack DevServer,所以原理完全相同,只是配置方式更加 Vue 化。
示例代码
vue.config.js
module.exports = {
devServer: {
port: 8080,
proxy: {
// 简单配置方式
'/api': 'http://localhost:3000',
// 详细配置方式
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': '/api/v1' // 将 /api 重写为 /api/v1
},
// 更多配置选项
ws: true, // 代理 websockets
headers: {
'X-Custom-Header': 'value'
}
},
// 多后端服务代理
'/auth': {
target: 'http://auth-server:4000',
changeOrigin: true
},
'/data': {
target: 'http://data-server:5000',
changeOrigin: true
}
}
}
};
在 Vue 组件中使用:
// axios 配置
import axios from 'axios';
// 创建实例
const api = axios.create({
baseURL: '/api', // 使用相对路径,由代理处理
timeout: 5000
});
// 在组件中使用
export default {
methods: {
async fetchUsers() {
try {
// 请求 /api/users → 代理转发到 http://localhost:3000/api/users
const response = await api.get('/users');
return response.data;
} catch (error) {
console.error('请求失败:', error);
}
}
}
}
Vite的proxy配置
原理
无论是 Vite 还是 Webpack DevServer,它们解决开发阶段跨域问题的核心原理是相同的:
它们会在本地启动一个开发服务器,这个服务器充当代理中间件。当你的前端应用向开发服务器发送请求时,开发服务器会将这个请求转发到目标后端服务器。由于跨域限制是浏览器的安全策略,而服务器之间没有这个限制,因此就绕过了跨域问题
过程可以简化为:
浏览器 (访问 http://localhost:5173) → Vite开发服务器 (接收前端请求) → 代理转发 → 后端API服务器 (http://localhost:8081)
代理配置
- 如果你的项目是基于 Vite 的(通常是 Vue 3 项目),就使用 vite.config.js 和 server.proxy 来配置代理 。
- 如果你的项目是基于 Vue CLI 的(通常是 Vue 2 项目),则使用 vue.config.js 和 devServer.proxy 来配置代理 。
对于Vue 3和Vue 2:
Vue3 用自己的开发服务器 - Vite 内置
Vue2 借助 Webpack DevServer - 通过 Vue CLI 集成
解决跨域原理相同 - 都是服务器层面转发
这些统称前端服务器 - 开发阶段的服务提供者
3. 后端配置 CORS
原理
CORS (跨源资源共享) 是 W3C(世界万维网联盟 World Wide Web Consortium) 标准,让后端服务器明确告诉浏览器允许哪些源、方法、头部进行跨域访问。
关键响应头:
-
Access-Control-Allow-Origin: 允许的源 -
Access-Control-Allow-Methods: 允许的 HTTP 方法 -
Access-Control-Allow-Headers: 允许的请求头 -
Access-Control-Allow-Credentials: 是否允许携带凭证
有两个主要的CORS配置方式:一是使用@CrossOrigin注解,二是使用CorsConfig配置类
1. @CrossOrigin 注解
使用位置
-
在控制器类上使用:表示该控制器下所有方法都允许跨域访问。
-
在控制器方法上使用:表示该方法允许跨域访问。
属性说明
-
origins:允许的源列表,可以是一个字符串数组。例如:origins = "http://localhost:8080"或origins = {"http://localhost:8080", "http://example.com"} -
allowedHeaders:允许的请求头列表,默认允许所有。 -
methods:允许的HTTP方法列表,例如{RequestMethod.GET, RequestMethod.POST}。 -
allowCredentials:是否允许凭证(如cookies),默认为false。 -
exposedHeaders:暴露给客户端的响应头列表。 -
maxAge:预检请求的缓存时间(秒),默认为1800秒。
示例
基本用法
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
// 在类级别配置 - 影响整个控制器
@CrossOrigin(origins = "http://localhost:8080")
@RestController
public class UserController {
// 这个方法继承类级别的CORS配置
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
// 方法级别配置会覆盖类级别配置
@CrossOrigin(origins = {"http://localhost:3000", "http://app.com"})
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
// 没有注解的方法使用类级别配置
@PutMapping("/users/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}
}
完整参数配置
@RestController
public class ApiController {
// 完整的CORS配置
@CrossOrigin(
origins = {
"http://localhost:3000",
"https://staging.example.com",
"https://production.example.com"
},
allowedHeaders = {
"Content-Type",
"Authorization",
"X-Requested-With"
},
methods = {
RequestMethod.GET,
RequestMethod.POST,
RequestMethod.PUT,
RequestMethod.DELETE,
RequestMethod.OPTIONS
},
allowCredentials = "true",
maxAge = 3600, // 预检请求缓存1小时
exposedHeaders = {"X-Custom-Header", "X-Another-Header"}
)
@GetMapping("/api/data")
public ResponseEntity<Data> getData() {
return ResponseEntity.ok(dataService.getData());
}
// 最简单的配置 - 允许所有源
@CrossOrigin("*")
@GetMapping("/public/data")
public Data getPublicData() {
return publicDataService.getData();
}
}
2. CorsConfig 配置类
通过一个配置类来全局配置CORS,适用于整个应用。这种方式可以避免在每个控制器上重复注解,并且可以集中管理。
实现方式
-
实现
WebMvcConfigurer接口,重写addCorsMappings方法。 -
使用
CorsRegistry来添加CORS映射。
示例
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 指定要应用CORS的路径模式
.allowedOrigins("http://localhost:8080", "http://example.com") // 允许的源
.allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的方法
.allowedHeaders("*") // 允许的请求头
.allowCredentials(true) // 是否允许凭证
.maxAge(3600); // 预检请求的缓存时间
}
}
更细粒度的配置
如果你需要更细粒度的控制,可以使用CorsConfigurationSource来定义多个CORS配置。
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:8080", "http://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
}
-
如果同时使用了全局配置和
@CrossOrigin注解,那么方法上的注解会覆盖全局配置。 -
在生产环境中,建议使用全局配置,并且严格限制允许的源,不要使用
*来允许所有源,除非是公开的API。 -
如果使用了Spring Security,需要注意Spring Security的CORS配置可能会覆盖这里的配置,需要在Spring Security中另外配置
结合Spring Security
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and() // 启用CORS支持
.csrf().disable() // 根据需求决定是否禁用CSRF
.authorizeRequests()
.anyRequest().authenticated();
}
// 如果你使用了自定义的CorsConfigurationSource,可以在这里声明一个Bean
@Bean
public CorsConfigurationSource corsConfigurationSource() {
// ... 同上例
}
}
Nginx
为什么使用Nginx?
-
方便管理:通过Nginx统一代理所有请求,不需要在多个后端服务上分别配置CORS,也不需要在开发服务器上配置代理。
-
生产环境中,Nginx可以作为静态文件服务器(部署前端资源)和反向代理(将API请求转发到后端服务),这样前后端就可以同源。
-
负载均衡:当有多个后端服务器时,Nginx可以分发请求到这些服务器,提高系统的可用性和性能。
-
解决Session共享问题:如果使用Session方式登录,并且有多台后端服务器,那么需要解决Session共享问题。Nginx可以通过负载均衡策略(如ip_hash)将同一用户的请求转发到同一台后端服务器,这样Session就可以存储在该服务器上。但是,更常见的做法是使用分布式Session方案(如Redis),而不是依赖Nginx的粘性会话(sticky session)。
为什么有时候我们选择Nginx而不是简单地配置后端CORS或开发服务器代理?
-
生产环境部署考虑:在生产环境中,我们通常会有多个服务,并且需要统一的入口。Nginx可以作为这个统一入口,简化网络结构。
-
安全性:CORS配置需要谨慎,如果配置不当(如允许任意源)可能会带来安全风险。而Nginx反向代理隐藏了后端服务,外部客户端只与Nginx交互,增加了安全性。
-
性能:Nginx可以处理静态资源,减轻后端服务器的压力,并且通过负载均衡提高整体性能。
但是,并不是所有情况都需要Nginx。对于小型项目或单一后端服务,直接在后端配置CORS可能更简单。开发阶段使用开发服务器的代理也很方便。
关于Session共享问题,如果使用Nginx的ip_hash负载均衡策略,可以确保同一客户端的请求总是落到同一台后端服务器,这样该客户端的Session就可以存储在这台服务器上。但是,如果该服务器宕机,那么Session会丢失,而且如果客户端IP发生变化(比如切换网络)也会被分配到不同的服务器。因此,更可靠的方案是使用集中的Session存储(如Redis、数据库等),这样无论请求被转发到哪台服务器,都可以访问到Session。
下载
Windows
从官网下载Nginx for Windows,解压后运行nginx.exe。
CentOS
sudo yum install nginx
Nginx 基本命令
# 启动 Nginx
sudo systemctl start nginx
# 或: sudo service nginx start
# 停止 Nginx
sudo systemctl stop nginx
# 重启 Nginx
sudo systemctl restart nginx
# 重新加载配置(不中断服务)
sudo systemctl reload nginx
# 查看状态
sudo systemctl status nginx
# 设置开机自启
sudo systemctl enable nginx
Nginx 配置文件结构
我们通常不会把所有配置都写在主配置文件里,而是采用模块化的方式,将不同功能的配置放在不同的文件中,然后通过 include 指令引入。
Nginx 配置文件结构
/etc/nginx/
├── nginx.conf # 主配置文件(入口)
├── conf.d/ # 通用配置目录
│ ├── gzip.conf # 压缩配置
│ ├── security.conf # 安全配置
│ └── proxy.conf # 代理通用配置
├── sites-available/ # 可用站点配置(仓库)
│ ├── static-site.conf # 静态网站
│ ├── api-server.conf # API服务
│ └── app-server.conf # 应用服务
├── sites-enabled/ # 启用站点配置(激活的站点)
│ ├── static-site.conf -> ../sites-available/static-site.conf
│ └── api-server.conf -> ../sites-available/api-server.conf
└── modules-available/ # 模块配置(一般不用动)
具体配置写在哪里
1. 主配置文件 nginx.conf
# /etc/nginx/nginx.conf
# 这里主要写全局配置,不写具体业务
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
http {
# 基础配置
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# 性能优化(这些属于高级配置)
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# 🔥 包含其他配置文件
include /etc/nginx/conf.d/*.conf; # 通用配置
include /etc/nginx/sites-enabled/*; # 站点配置
}
2. 通用配置 conf.d/ 目录
# 创建通用配置文件
cd /etc/nginx/conf.d/
# 创建以下文件:
touch gzip.conf security.conf proxy-common.conf
/etc/nginx/conf.d/gzip.conf
# 压缩配置 - 所有站点共享
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
/etc/nginx/conf.d/security.conf
# 安全配置 - 所有站点共享
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
server_tokens off;
/etc/nginx/conf.d/proxy-common.conf
# 代理通用配置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
3.站点配置 sites-available/ 和 sites-enabled/
创建站点配置文件的步骤:
# 1. 在 sites-available 创建配置
sudo nano /etc/nginx/sites-available/my-static-site.conf
# 2. 创建符号链接到 sites-enabled(启用站点)
sudo ln -s /etc/nginx/sites-available/my-static-site.conf /etc/nginx/sites-enabled/
# 3. 测试配置
sudo nginx -t
# 4. 重新加载
sudo systemctl reload nginx
具体场景配置示例
场景1:静态文件服务器
文件: /etc/nginx/sites-available/static-site.conf
server {
listen 80;
server_name static.example.com;
root /var/www/static-site;
index index.html index.htm;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML 不缓存
location ~* \.html$ {
expires -1;
}
# 错误页面
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
}
场景2:反向代理(API服务)
文件: /etc/nginx/sites-available/api-server.conf
server {
listen 80;
server_name api.example.com;
# CORS 配置
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
# 预检请求
if ($request_method = 'OPTIONS') {
return 204;
}
location /api/ {
# 包含通用代理配置
include /etc/nginx/conf.d/proxy-common.conf;
proxy_pass http://localhost:8080/;
# 特定超时配置
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
}
场景3:负载均衡
文件: /etc/nginx/sites-available/load-balancer.conf
# 上游服务器定义
upstream backend_servers {
server 192.168.1.10:8080 weight=3;
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 weight=1;
}
server {
listen 80;
server_name app.example.com;
location / {
include /etc/nginx/conf.d/proxy-common.conf;
proxy_pass http://backend_servers;
# 健康检查
proxy_next_upstream error timeout http_500 http_502 http_503;
}
}
场景4:前后端分离
文件: /etc/nginx/sites-available/fullstack-app.conf
server {
listen 80;
server_name myapp.com;
# 前端静态文件
location / {
root /var/www/frontend/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
# API 代理
location /api/ {
include /etc/nginx/conf.d/proxy-common.conf;
proxy_pass http://backend:8080/api/;
}
# 文件上传
location /upload/ {
include /etc/nginx/conf.d/proxy-common.conf;
proxy_pass http://backend:8080/upload/;
client_max_body_size 100M;
}
# WebSocket
location /ws/ {
include /etc/nginx/conf.d/proxy-common.conf;
proxy_pass http://backend:8080/ws/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
高级配置的位置
性能优化配置
文件: /etc/nginx/conf.d/performance.conf
# 连接优化
client_body_buffer_size 128k;
client_max_body_size 10M;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
# 超时配置
client_header_timeout 10s;
client_body_timeout 10s;
send_timeout 10s;
# 文件传输优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
SSL/HTTPS 配置
文件: /etc/nginx/sites-available/ssl-site.conf
server {
listen 443 ssl http2;
server_name secure.example.com;
# SSL 证书路径
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# SSL 配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
location / {
proxy_pass http://backend:8080;
include /etc/nginx/conf.d/proxy-common.conf;
}
}
# HTTP 重定向
server {
listen 80;
server_name secure.example.com;
return 301 https://$server_name$request_uri;
}
快速开始模板
最简单的单文件配置(开发环境)
# /etc/nginx/nginx.conf - 开发环境简单配置
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
# 一个简单的服务器配置
server {
listen 80;
server_name localhost;
# 静态文件
location / {
root /var/www/html;
index index.html;
}
# API 代理
location /api/ {
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
}
}
}
配置管理最佳实践
1. 启用/禁用站点
# 启用站点
sudo ln -s /etc/nginx/sites-available/my-site.conf /etc/nginx/sites-enabled/
# 禁用站点
sudo rm /etc/nginx/sites-enabled/my-site.conf
# 重新加载配置
sudo nginx -t && sudo systemctl reload nginx
2. 配置检查流程
# 1. 检查语法
sudo nginx -t
# 2. 测试配置(不中断服务)
sudo nginx -T
# 3. 重新加载
sudo systemctl reload nginx
# 4. 查看状态
sudo systemctl status nginx
3. 备份配置
# 备份整个配置
sudo tar -czf nginx-backup-$(date +%Y%m%d).tar.gz /etc/nginx/
# 备份单个站点
sudo cp /etc/nginx/sites-available/my-site.conf /etc/nginx/sites-available/my-site.conf.backup
总结
配置写在哪里:
-
nginx.conf- 全局基础配置 -
conf.d/- 通用功能配置(压缩、安全等) -
sites-available/- 所有站点配置(仓库) -
sites-enabled/- 当前启用的站点(符号链接)
建议:
-
开发环境可以简单点,直接在主配置文件写
-
生产环境一定要按目录组织,便于管理
-
每个站点一个文件,功能相似的配置放一起
1614

被折叠的 条评论
为什么被折叠?



