开发中的跨域

跨域问题是由于浏览器的同源策略导致的,它阻止从一个源加载的文档或脚本与来自另一个源的资源进行交互。为了解决开发环境下的跨域问题,我们通常采用以下三种方式:

  1. Webpack DevServer 的 proxy 配置

  2. Vue CLI 的 devServer.proxy(实际上也是基于 Webpack DevServer)

  3. 后端配置 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?

  1. 方便管理:通过Nginx统一代理所有请求,不需要在多个后端服务上分别配置CORS,也不需要在开发服务器上配置代理。

  2. 生产环境中,Nginx可以作为静态文件服务器(部署前端资源)和反向代理(将API请求转发到后端服务),这样前后端就可以同源。

  3. 负载均衡:当有多个后端服务器时,Nginx可以分发请求到这些服务器,提高系统的可用性和性能。

  4. 解决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/ - 当前启用的站点(符号链接)

建议:

  • 开发环境可以简单点,直接在主配置文件写

  • 生产环境一定要按目录组织,便于管理

  • 每个站点一个文件,功能相似的配置放一起

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值