跨域问题深度解析:原因、表现与SpringBoot实战解决方案

3 阅读8分钟

在前后端分离开发模式中,跨域是一个绕不开的技术难题。很多开发者初次遇到跨域时,都会陷入“接口能通但前端拿不到数据”的困惑,甚至误以为是后端接口出现故障。其实跨域并非接口问题,而是浏览器为了保障安全设置的同源策略限制。本文将从跨域的本质出发,拆解其产生原因、典型表现,再结合实战代码,详解SpringBoot环境下四种常用的跨域解决方案,帮助开发者快速搞定跨域难题。

一、为什么会出现跨域?核心是浏览器的同源策略

跨域的产生,根源在于浏览器的同源策略——这是浏览器最基础的安全机制,目的是防止恶意网站通过JavaScript脚本窃取其他网站的敏感数据,比如用户登录态、个人信息等。所谓“同源”,就是指两个网络地址的协议、域名、端口必须完全一致,只要有一个维度不同,就会被浏览器判定为不同源,从而触发跨域限制。

1. 同源判定的3个核心维度

浏览器判断两个地址是否同源,严格遵循以下3个标准,缺一不可:

2. 常见跨域场景示例

结合日常开发场景,以下几种情况均会触发跨域,大家可以对照自查:


# 场景1:协议不同
前端:http://localhost:8080 → 后端:https://localhost:8080 (跨域)

# 场景2:域名不同
前端:http://www.a.com → 后端:http://www.b.com (跨域)

# 场景3:端口不同
前端:http://localhost:8080 → 后端:http://localhost:9090 (跨域)

# 场景4:域名别名不同
前端:http://localhost:8080 → 后端:http://127.0.0.1:8080 (跨域)
    

这里需要特别注意:跨域是浏览器专属的限制,服务器与服务器之间的通信(比如Java后端通过HttpClient调用其他服务接口),不会受到同源策略约束,因此不存在跨域问题。

二、跨域的典型表现:请求发得出,数据收不到

很多开发者对跨域的误解是“接口请求失败”,但实际情况是:跨域请求本身能正常到达后端服务器,后端也会正常处理请求并返回数据,但浏览器在接收响应时,会检查响应头中是否包含合规的CORS(跨域资源共享)头,若没有则会拦截响应数据,并在控制台抛出跨域报错,导致前端无法获取数据。

1. 浏览器控制台典型跨域报错

当出现跨域时,浏览器控制台会输出类似以下的报错信息,这是判断跨域问题的核心依据:


Access to fetch at 'http://localhost:9090/api/user' from origin 'http://localhost:8080' has been blocked by CORS policy: 
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    

2. 实战模拟:复现跨域场景

为了更直观地感受跨域现象,我们通过简单的前后端代码,模拟一个跨域场景,帮助大家理解其表现。

后端(SpringBoot,端口9090):编写一个简单的用户接口


package com.example.corsdemo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/user")
    public String getUserInfo() {
        // 模拟返回用户信息
        return "{'username': 'test', 'age': 20}";
    }
}
    

后端配置文件(application.yml),指定端口9090:


server:
  port: 9090
    

前端(HTML+JS,运行在8080端口):发起跨域请求

启动后端和前端后,点击“获取用户信息”按钮,前端会显示请求失败,打开浏览器控制台,就能看到跨域报错,这就是跨域的典型表现——请求发出去了,但数据被浏览器拦截了。

三、SpringBoot解决跨域:4种实战方案(覆盖所有场景)

SpringBoot解决跨域的核心思路是:让后端接口返回合规的CORS跨域响应头,告诉浏览器“该接口允许跨域访问”,从而放行响应数据。下面介绍4种常用方案,分别适配不同的开发场景,大家可根据项目需求灵活选择。

方案一:全局跨域配置(单体项目首选)

全局跨域配置是最常用的方案,通过编写配置类,让整个项目的所有接口都支持跨域,无需在每个接口单独配置,开发效率高、无侵入性,适合单体项目使用。


package com.example.corsdemo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 全局跨域配置类,所有接口统一支持跨域
 */
@Configuration
public class CorsGlobalConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 匹配所有接口路径
                .allowedOriginPatterns("*") // 允许所有跨域源(生产环境建议指定具体域名)
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许所有请求方法,必须放行OPTIONS预检请求
                .allowedHeaders("*") // 允许所有请求头(如token、Content-Type)
                .allowCredentials(true) // 允许携带Cookie,前后端分离带登录态必备
                .maxAge(3600); // 预检请求有效期(秒),期间无需重复发起预检
    }
}
    

配置说明:SpringBoot 2.4及以上版本,推荐使用allowedOriginPatterns(""),替代过时的allowedOrigins(""),可解决通配符与allowCredentials=true同时配置时的冲突问题。

方案二:局部跨域配置(精准控制接口)

当项目中只有部分接口需要跨域,其余接口无需开放跨域权限时,适合使用局部配置。通过@CrossOrigin注解,可实现接口级或控制器级的精准控制,其优先级高于全局配置。

方式1:接口级配置(单个接口生效)


package com.example.corsdemo.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class UserController {

    // 仅当前接口允许跨域,其余接口不受影响
    @CrossOrigin(origins = "*", allowCredentials = true, maxAge = 3600)
    @GetMapping("/user")
    public String getUserInfo() {
        return "{'username': 'test', 'age': 20}";
    }

    // 该接口不配置跨域,不允许跨域访问
    @GetMapping("/user/count")
    public String getUserCount() {
        return "100";
    }
}
    

方式2:控制器级配置(整个控制器接口生效)


package com.example.corsdemo.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

// 该控制器下所有接口统一允许跨域
@RestController
@RequestMapping("/order")
@CrossOrigin(origins = "*", allowCredentials = true, maxAge = 3600)
public class OrderController {

    @GetMapping("/list")
    public String getOrderList() {
        return "订单列表(支持跨域)";
    }

    @GetMapping("/detail")
    public String getOrderDetail() {
        return "订单详情(支持跨域)";
    }
}
    

方案三:Nginx反向代理(生产环境优选)

跨域是浏览器的限制,服务器之间通信无跨域问题。通过Nginx将前端项目和后端接口代理到同一个域名和端口,消除同源差异,既能解决跨域,又能隐藏后端真实端口,提升系统安全性,适合生产环境使用。

假设前端运行在8080端口,后端运行在9090端口,Nginx核心配置如下:


server {
    listen 80; # Nginx监听80端口
    server_name localhost; # 代理域名

    # 代理前端项目:http://localhost → 前端服务(8080端口)
    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # 代理后端接口:http://localhost/api → 后端服务(9090端口)
    location /api {
        proxy_pass http://localhost:9090;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        rewrite ^/api/(.*)$ /$1 break; # 去掉/api前缀,适配后端真实接口
    }
}
    

配置后,前端请求后端接口时,只需写http://localhost/api/user,无需指定9090端口,Nginx会自动转发请求,浏览器判定同源,不会出现跨域问题。

方案四:SpringCloud网关跨域(微服务专属)

在微服务架构中,所有请求都会经过SpringCloud Gateway网关,此时只需在网关层统一配置跨域,所有微服务无需单独配置,避免出现跨域头重复导致的跨域失败。

SpringCloud Gateway核心配置(application.yml):


spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求路径
            allowed-origins: "*"        # 允许所有跨域源
            allowed-methods: "*"        # 允许所有请求方法
            allowed-headers: "*"        # 允许所有请求头
            allow-credentials: true     # 允许携带Cookie
            max-age: 3600               # 预检请求有效期(秒)
    

四、核心总结

跨域的本质是浏览器的同源策略限制,解决跨域的核心是让后端返回合规的CORS跨域响应头。不同场景对应不同的解决方案,选择合适的方案能大幅提升开发效率:

  • 单体项目:优先使用全局跨域配置,一次配置,全项目生效;
  • 部分接口跨域:使用@CrossOrigin局部注解,精准控制,灵活适配;
  • 生产环境:优先使用Nginx反向代理,兼顾性能与安全性;
  • 微服务架构:使用SpringCloud网关统一配置,避免重复配置。

掌握以上方案,就能轻松应对开发中绝大多数跨域问题,同时理解跨域的核心逻辑,也能在面试中从容应对相关提问。