Skip to content

跨域问题

问题表现

前端请求后端接口时出现以下错误:

Access to XMLHttpRequest at 'http://localhost:8080/api/v1/users' 
from origin 'http://localhost:5173' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

原因分析

跨域是浏览器的同源策略限制:

对比项前端后端是否同源
协议httphttp
域名localhostlocalhost
端口51738080

端口不同,触发跨域限制。

解决方案

方案 1:后端配置 CORS(推荐)

Java Spring Boot

java
@Configuration
public class CorsConfig {
    
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        
        // 允许的域名
        config.addAllowedOriginPattern("*");
        
        // 允许的请求头
        config.addAllowedHeader("*");
        
        // 允许的方法
        config.addAllowedMethod("*");
        
        // 允许携带凭证(Cookie)
        config.setAllowCredentials(true);
        
        // 暴露的响应头
        config.addExposedHeader("Authorization");
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        
        return new CorsFilter(source);
    }
}

Node NestJS

typescript
// main.ts
app.enableCors({
  origin: ['http://localhost:5173'],
  credentials: true,
  allowedHeaders: ['Content-Type', 'Authorization'],
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
})

Go Gin

go
func CorsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Header("Access-Control-Allow-Origin", "http://localhost:5173")
        c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
        c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization")
        c.Header("Access-Control-Allow-Credentials", "true")
        
        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }
        
        c.Next()
    }
}

方案 2:Vite 代理(开发环境)

typescript
// vite.config.ts
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '/api')
      }
    }
  }
})

前端请求 /api/v1/users → 代理到 http://localhost:8080/api/v1/users

优点:无需后端配置,适合开发环境
缺点:生产环境不适用

方案 3:Nginx 反向代理(生产环境)

nginx
server {
    listen 80;
    server_name vue.youlai.tech;
    
    # 前端
    location / {
        root /var/www/html;
        try_files $uri $uri/ /index.html;
    }
    
    # 后端 API
    location /api {
        proxy_pass http://backend:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # CORS 头
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET,POST,PUT,DELETE,OPTIONS';
        add_header Access-Control-Allow-Headers 'Content-Type,Authorization';
        
        if ($request_method = 'OPTIONS') {
            return 204;
        }
    }
}

方案 4:JSONP(不推荐)

只支持 GET 请求,不适用于现代 Web 应用。

常见问题

原因:跨域请求默认不携带 Cookie

解决

前端:

typescript
axios.defaults.withCredentials = true

后端:

java
config.setAllowCredentials(true)
config.addAllowedOrigin("http://localhost:5173") // 不能用 *

2. OPTIONS 预检请求失败

原因:浏览器先发送 OPTIONS 请求检查是否允许跨域

解决

java
if (request.getMethod().equals("OPTIONS")) {
    response.setStatus(204)
    return
}

3. SSE 跨域问题

问题:EventSource 不支持自定义请求头

解决

typescript
// 使用查询参数传递 Token
const eventSource = new EventSource(
  `/api/v1/sse/connect?accessToken=${token}`
)

后端:

java
@GetMapping("/sse/connect")
public void sseConnect(@RequestParam String accessToken, 
                       HttpServletResponse response) {
    // 验证 Token
    // ...
    
    response.setHeader("Access-Control-Allow-Origin", "*")
}

4. WebSocket 跨域

typescript
const ws = new WebSocket('ws://localhost:8080/ws')

后端需要配置 WebSocket CORS:

java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/ws")
                 .setAllowedOrigins("*");
    }
}

调试技巧

Chrome DevTools

查看 Network 标签:

Response Headers:
  Access-Control-Allow-Origin: http://localhost:5173
  Access-Control-Allow-Credentials: true
  Access-Control-Allow-Methods: GET, POST, PUT, DELETE

curl 测试

bash
curl -X OPTIONS http://localhost:8080/api/v1/users \
  -H "Origin: http://localhost:5173" \
  -H "Access-Control-Request-Method: GET" \
  -v

最佳实践

环境推荐方案
开发环境Vite 代理
测试环境后端 CORS
生产环境Nginx 反向代理

相关链接

基于 MIT 许可发布 · 由 ❤️ 和 ☕ 驱动 · 支持作者