Spring Boot 跨域解决方案详解:从原理到实战(初学者友好版)

Spring Boot 跨域解决方案详解:从原理到实战(初学者友好版)

Spring Boot 跨域解决方案详解:从原理到实战(初学者友好版)

        结合我们之前学习的 Spring Boot Web 开发、拦截器等知识,本文将聚焦跨域问题—— 这是前后端分离架构中最常见的痛点之一。我们会从 “什么是跨域”“为什么会有跨域问题” 入手,详细拆解两种核心解决方案(@CrossOrigin注解、全局 CORS 配置),搭配完整的前后端实战代码,帮我们彻底搞懂跨域配置的每一个细节,避免初学者常见的 “配置了但不生效” 问题。

一、先搞懂基础:什么是跨域?为什么会有跨域问题?

        在学解决方案前,我们必须先理解 “跨域” 的本质 —— 它不是后端的问题,而是浏览器的安全限制导致的。

1.1 同源策略:跨域问题的根源

        浏览器为了防止恶意网站窃取数据,制定了 “同源策略”:只有当请求的 “协议、域名、端口” 三者完全相同时,才允许发送 AJAX 请求,否则就是 “跨域请求”,浏览器会拦截响应。

对比维度 示例 1(当前页面) 示例 2(请求目标) 是否同源 结论(是否跨域)
协议 http://localhost:8080 http://localhost:8081 否(端口不同)
域名 http://localhost:8080 http://127.0.0.1:8080 否(域名不同)
端口 https://baidu.***:80 https://baidu.***:443 否(端口不同)
三者相同 http://localhost:8080 http://localhost:8080

我们的实际场景:前端项目运行在http://localhost:8080(比如 Vue/React 项目),后端 Spring Boot 项目运行在http://localhost:8081,此时前端发送 AJAX 请求到后端,就会触发跨域拦截。

1.2 跨域报错:浏览器的典型提示

当没有配置 CORS 时,前端发送跨域请求,浏览器控制台会报类似错误:

 A***ess to fetch at 'http://localhost:8081/api/hello' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'A***ess-Control-Allow-Origin' header is present on the requested resource.
  • 错误原因:后端响应中没有包含A***ess-Control-Allow-Origin头,浏览器认为这个跨域请求不安全,拒绝接收响应。

1.3 CORS 机制:解决跨域的标准方案

        CORS(Cross-Origin Resource Sharing,跨域资源共享)是 W3C 制定的标准,允许后端通过HTTP 响应头告诉浏览器:“这个源的请求是安全的,你可以接收响应”。

核心原理:

  1. 前端发送跨域请求时,浏览器会先发送一个 “预检请求(OPTIONS 请求)”,询问后端 “是否允许这个源的请求”;

  2. 后端返回包含 CORS 头的响应(如A***ess-Control-Allow-Origin: http://localhost:8080);

  3. 浏览器验证 CORS 头,如果允许,则发送真正的请求(GET/POST 等),否则拦截。

二、Spring Boot 跨域解决方案一:@CrossOrigin 注解(方法级配置)

   @CrossOrigin是 Spring 提供的声明式注解,可以直接加在 Controller 方法或类上,快速开启单个接口或单个 Controller 的跨域支持,适合简单场景。

2.1 注解作用与参数

参数名 作用 示例值 默认值
origins 允许跨域的源(协议 + 域名 + 端口) {"http://localhost:8080", "https://www.example.***"} *(允许所有源,不推荐生产环境)
allowedMethods 允许跨域的 HTTP 方法 {"GET", "POST", "PUT", "DELETE"} 允许请求本身的方法(如 GET 请求只允许 GET)
allowedHeaders 允许跨域请求携带的请求头 {"Content-Type", "Authorization"} *(允许所有请求头)
allowCredentials 是否允许携带 Cookie(如登录态) true/false false(不允许)
maxAge 预检请求的缓存时间(秒),避免重复预检 3600(1 小时) 1800(30 分钟)

2.2 实战 1:方法级配置(单个接口跨域)

        我们在后端 Controller 的单个方法上添加@CrossOrigin,允许http://localhost:8080的跨域请求:

步骤 1:后端 Controller 代码
 package ***.Lh.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 ApiController {
 ​
     // 方法级跨域配置:只允许http://localhost:8080的GET请求
     @CrossOrigin(
         origins = "http://localhost:8080",  // 允许的源
         allowedMethods = "GET",           // 允许的方法
         allowCredentials = true,          // 允许携带Cookie
         maxAge = 3600                     // 预检请求缓存1小时
     )
     @GetMapping("/hello")
     public String sayHello() {
         // 返回简单字符串,供前端跨域请求
         return "Hello from Spring Boot! 这是跨域GET请求的响应";
     }
 }
步骤 2:前端测试代码(HTML + fetch)

        创建cross-test.html,运行在http://localhost:8080(比如用另一个 Spring Boot 项目的templates目录,或用 Nginx 部署):

<!DOCTYPE html>
 <html lang="zh-***">
 <head>
     <meta charset="UTF-8">
     <title>跨域测试(方法级配置)</title>
 </head>
 <body>
     <h1>跨域GET请求测试</h1>
     <button onclick="sendGetRequest()">发送跨域GET请求</button>
     <div id="result" style="margin-top: 20px; color: green;"></div>
 ​
     <script>
         function sendGetRequest() {
             // 发送跨域请求到后端8081端口的/api/hello
             fetch('http://localhost:8081/api/hello', {
                 method: 'GET',
                 credentials: 'include'  // 必须加,否则Cookie不会携带(对应后端allowCredentials=true)
             })
             .then(response => {
                 if (!response.ok) {
                     throw new Error('跨域请求失败');
                 }
                 return response.text();
             })
             .then(data => {
                 // 显示响应结果
                 document.getElementById('result').innerText = '响应成功:' + data;
             })
             .catch(error => {
                 document.getElementById('result').innerText = '响应失败:' + error.message;
                 document.getElementById('result').style.color = 'red';
             });
         }
     </script>
 </body>
 </html>
步骤 3:测试流程
  1. 启动后端项目,端口 8081;

  2. 启动前端项目(端口 8080),访问http://localhost:8080/cross-test.html

  3. 点击 “发送跨域 GET 请求”,页面显示 “响应成功:Hello from Spring Boot! 这是跨域 GET 请求的响应”,证明跨域配置生效。

2.3 实战 2:类级配置(整个 Controller 跨域)

        如果一个 Controller 的所有接口都需要跨域,可以把@CrossOrigin加在 Controller 类上,作用于所有方法:

// 类级跨域配置:整个ApiController的所有接口都允许跨域
 @CrossOrigin(origins = "http://localhost:8080", allowCredentials = true)
 @RestController
 @RequestMapping("/api")
 public class ApiController {
 ​
     // 无需再加@CrossOrigin,继承类上的配置
     @GetMapping("/hello")
     public String sayHello() {
         return "Hello from Class-Level CORS!";
     }
 ​
     // 无需再加@CrossOrigin
     @PostMapping("/user")
     public String createUser() {
         return "User created su***essfully!";
     }
 }

2.4 适用场景与局限性

适用场景 局限性
单个接口或单个 Controller 需要跨域 不适合多个 Controller,需重复加注解
跨域规则简单(固定源、固定方法) 生产环境中origins="*"不安全,且不支持携带 Cookie
快速测试跨域功能 无法统一管理跨域规则,维护成本高

三、Spring Boot 跨域解决方案二:全局 CORS 配置(推荐)

        对于多 Controller、复杂跨域规则的项目,推荐用全局 CORS 配置—— 通过实现WebMv***onfigurer接口的addCorsMappings方法,统一配置所有接口的跨域规则,一次配置,全局生效。

3.1 全局配置的核心参数(与 @CrossOrigin 一致)

        全局配置的参数和@CrossOrigin完全对应,只是配置方式从注解变成了代码,核心参数:

  • addMapping("/**"):对所有接口生效(/**表示所有路径,也可指定/api/**只对 /api 前缀接口生效);

  • allowedOrigins("http://localhost:8080"):允许的源;

  • allowedMethods("GET", "POST", "PUT", "DELETE"):允许的 HTTP 方法;

  • allowedHeaders("*"):允许的请求头(如Content-TypeAuthorization);

  • allowCredentials(true):允许携带 Cookie;

  • maxAge(3600):预检请求缓存 1 小时。

3.2 实战:全局 CORS 配置(完整后端代码)

        我们搭建一个完整的后端项目,包含启动类、全局配置类、Controller、实体类,实现 GET 和 POST 跨域请求。

步骤 1:项目结构
 cors-demo(项目根目录)
 ├─ src/main/java/***/zh/corsdemo/
 │  ├─ CorsDemoApplication.java(启动类)
 │  ├─ config/WebConfig.java(全局CORS配置)
 │  ├─ controller/ApiController.java(接口)
 │  └─ entity/User.java(实体类)
 └─ src/main/resources/application.yml(配置文件)
步骤 2:启动类(CorsDemoApplication.java)
package ***.lh.corsdemo;
 ​
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 ​
 @SpringBootApplication
 public class CorsDemoApplication {
     public static void main(String[] args) {
         // 启动后端项目,端口8081(在application.yml中配置)
         SpringApplication.run(CorsDemoApplication.class, args);
     }
 }
步骤 3:全局 CORS 配置类(WebConfig.java)
 package ***.lh.corsdemo.config;
 ​
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.CorsRegistry;
 import org.springframework.web.servlet.config.annotation.WebMv***onfigurer;
 ​
 @Configuration  // 标记为配置类,Spring启动时加载
 public class WebConfig implements WebMv***onfigurer {
 ​
     // 重写addCorsMappings,配置全局跨域
     @Override
     public void addCorsMappings(CorsRegistry registry) {
         registry.addMapping("/**")  // 1. 对所有接口生效
                 .allowedOrigins("http://localhost:8080")  // 2. 允许的前端源(生产环境用具体域名,如"https://www.zh.***")
                 .allowedMethods("GET", "POST", "PUT", "DELETE")  // 3. 允许的HTTP方法
                 .allowedHeaders("*")  // 4. 允许的请求头(如Content-Type、Authorization)
                 .allowCredentials(true)  // 5. 允许携带Cookie(登录态需要)
                 .maxAge(3600);  // 6. 预检请求缓存1小时,减少OPTIONS请求次数
     }
 }
步骤 4:实体类(User.java)

        用于接收前端 POST 请求的参数:

package ***.lh.corsdemo.entity;
 ​
 import lombok.Data;
 ​
 // Lombok注解:自动生成Getter/Setter/toString
 @Data
 public class User {
     private Long id;       // 用户ID(后端生成)
     private String name;   // 用户名(前端传入)
     private String email;  // 邮箱(前端传入)
 }
步骤 5:Controller(ApiController.java)

        提供 GET 和 POST 接口,无需加@CrossOrigin(全局配置已生效):

 package ***.lh.corsdemo.controller;
 ​
 import ***.lh.corsdemo.entity.User;
 import org.springframework.web.bind.annotation.*;
 ​
 @RestController
 public class ApiController {
 ​
     // 1. 跨域GET接口:返回简单字符串
     @GetMapping("/api/hello")
     public String sayHello() {
         return "Hello from Global CORS! 这是跨域GET响应";
     }
 ​
     // 2. 跨域POST接口:接收JSON参数,返回创建的User
     @PostMapping("/user")
     public User createUser(@RequestBody User user) {
         // 模拟后端生成ID
         user.setId(1L);
         // 返回包含ID的User对象
         return user;
     }
 }
步骤 6:配置文件(application.yml)

        设置后端端口为 8081:

 server:
   port: 8081  # 后端端口,与前端8080区分,避免冲突

3.3 前端实战:完整测试页面(GET+POST)

        创建global-cors-test.html,运行在http://localhost:8080,测试两种跨域请求:

<!DOCTYPE html>
 <html lang="zh-***">
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>全局CORS跨域测试</title>
     <style>
         .section { margin: 30px 0; padding: 20px; border: 1px solid #eee; border-radius: 8px; }
         button { padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
         button:hover { background: #45a049; }
         .result { margin-top: 15px; padding: 10px; border-left: 4px solid green; }
         .error { border-left-color: red; color: red; }
         .form-group { margin: 10px 0; }
         input { padding: 8px; width: 300px; }
     </style>
 </head>
 <body>
     <div class="container" style="max-width: 800px; margin: 0 auto;">
         <h1>全局CORS跨域测试(8080 → 8081)</h1>
 ​
         <!-- 1. GET请求测试 -->
         <div class="section">
             <h2>1. GET请求测试</h2>
             <button onclick="sendGetRequest()">发送GET请求</button>
             <div id="getResult" class="result"></div>
         </div>
 ​
         <!-- 2. POST请求测试 -->
         <div class="section">
             <h2>2. POST请求测试(提交用户信息)</h2>
             <div class="form-group">
                 <label>用户名:</label>
                 <input type="text" id="name" placeholder="输入用户名">
             </div>
             <div class="form-group">
                 <label>邮箱:</label>
                 <input type="text" id="email" placeholder="输入邮箱">
             </div>
             <button onclick="sendPostRequest()">发送POST请求</button>
             <div id="postResult" class="result"></div>
         </div>
     </div>
 ​
     <script>
         // 1. 发送跨域GET请求
         function sendGetRequest() {
             fetch('http://localhost:8081/api/hello', {
                 method: 'GET',
                 credentials: 'include'  // 携带Cookie,对应后端allowCredentials=true
             })
             .then(res => {
                 if (!res.ok) throw new Error('GET请求失败');
                 return res.text();
             })
             .then(data => {
                 const resultDiv = document.getElementById('getResult');
                 resultDiv.innerText = 'GET响应成功:' + data;
                 resultDiv.classList.remove('error');
             })
             .catch(err => {
                 const resultDiv = document.getElementById('getResult');
                 resultDiv.innerText = 'GET响应失败:' + err.message;
                 resultDiv.classList.add('error');
             });
         }
 ​
         // 2. 发送跨域POST请求(JSON参数)
         function sendPostRequest() {
             const name = document.getElementById('name').value;
             const email = document.getElementById('email').value;
 ​
             if (!name || !email) {
                 alert('请填写用户名和邮箱!');
                 return;
             }
 ​
             fetch('http://localhost:8081/user', {
                 method: 'POST',
                 headers: {
                     'Content-Type': 'application/json',  // 告诉后端参数是JSON格式
                 },
                 body: JSON.stringify({ name, email }),  // 把表单数据转成JSON字符串
                 credentials: 'include'  // 携带Cookie
             })
             .then(res => {
                 if (!res.ok) throw new Error('POST请求失败');
                 return res.json();  // 后端返回JSON,用res.json()解析
             })
             .then(data => {
                 const resultDiv = document.getElementById('postResult');
                 resultDiv.innerText = `POST响应成功:\nID: ${data.id}\n用户名: ${data.name}\n邮箱: ${data.email}`;
                 resultDiv.classList.remove('error');
             })
             .catch(err => {
                 const resultDiv = document.getElementById('postResult');
                 resultDiv.innerText = 'POST响应失败:' + err.message;
                 resultDiv.classList.add('error');
             });
         }
     </script>
 </body>
 </html>

3.4 运行与测试步骤

步骤 1:启动后端项目
  1. 运行CorsDemoApplication.java

  2. 查看控制台,确认 “Tomcat started on port (s): 8081 (http)”,证明后端启动成功。

步骤 2:部署前端页面

        前端页面需要运行在http://localhost:8080(符合后端allowedOrigins配置),推荐两种方式:

  • 方式 1:用另一个 Spring Boot 项目(端口 8080),将global-cors-test.html放在src/main/resources/templates目录,通过 Controller 跳转访问;

  • 方式 2:用 Nginx 部署,配置端口 8080,指向 HTML 文件所在目录。

这里以 “方式 1” 为例,新建前端 Spring Boot 项目(端口 8080):

  1. 引入 Thymeleaf 依赖(用于页面跳转):

    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-thymeleaf</artifactId>
     </dependency>
  2. 编写前端 Controller(跳转页面):

    @Controller
     public class FrontController {
         @RequestMapping("/global-cors-test")
         public String toCorsTest() {
             return "global-cors-test";  // 对应templates/global-cors-test.html
         }
     }
  3. 启动前端项目,访问http://localhost:8080/global-cors-test

步骤 3:测试跨域请求
  1. 测试 GET 请求:点击 “发送 GET 请求”,页面显示 “GET 响应成功:Hello from Global CORS! 这是跨域 GET 响应”;

  2. 测试 POST 请求:填写用户名(如 “张三”)和邮箱(如 “zhangsan@test.***”),点击 “发送 POST 请求”,页面显示

     POST响应成功:
     ID: 1
     用户名: 张三
     邮箱: zhangsan@test.***
  3. 结论:全局 CORS 配置生效,GET 和 POST 跨域请求均成功。

3.5 全局配置的优势与适用场景

优势 适用场景
一次配置,所有接口生效 多 Controller、多接口需要跨域
统一管理跨域规则,维护成本低 生产环境,需要严格控制源、方法、头
支持复杂规则(如不同路径不同配置) 部分接口需要特殊跨域规则(如/admin/**只允许特定源)

四、跨域配置常见问题与解决方案(初学者避坑)

很多初学者配置了跨域但不生效,大多是因为忽略了这些细节:

4.1 问题 1:allowCredentials=true 但前端没加 credentials: 'include'

  • 现象:后端配置allowCredentials=true,但前端携带的 Cookie 没有传递到后端;

  • 原因:前端 fetch 请求必须加credentials: 'include',否则浏览器不会携带 Cookie;

  • 解决方案:

fetch('http://localhost:8081/api/hello', {
     method: 'GET',
     credentials: 'include'  // 必须加,对应后端allowCredentials=true
 });

4.2 问题 2:origins 设为 "*" 但 allowCredentials=true

  • 现象:后端配置allowedOrigins("*")allowCredentials(true),前端报错 “The value of the 'A***ess-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'”;

  • 原因:浏览器安全限制,当allowCredentials=true时,allowedOrigins不能为 “*”(必须指定具体源);

  • 解决方案:

    // 错误:origins="*" + allowCredentials=true
     // registry.addMapping("/**").allowedOrigins("*").allowCredentials(true);
     ​
     // 正确:指定具体源
     registry.addMapping("/**").allowedOrigins("http://localhost:8080").allowCredentials(true);

4.3 问题 3:拦截器拦截了 OPTIONS 预检请求

  • 现象:前端发送跨域请求,后端拦截器报 “未登录”,但实际是 OPTIONS 预检请求;

  • 原因:跨域请求前,浏览器会先发 OPTIONS 请求询问后端是否允许跨域,拦截器误将 OPTIONS 请求当成普通请求拦截;

  • 解决方案:在拦截器中放行 OPTIONS 请求:

     @***ponent
     public class LoginInterceptor implements HandlerInterceptor {
         @Override
         public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
             // 放行OPTIONS预检请求
             if ("OPTIONS".equals(request.getMethod())) {
                 return true;
             }
             // 其他拦截逻辑(如登录验证)...
         }
     }

4.4 问题 4:前端用 file 协议打开 HTML(file:///C:/...)

  • 现象:直接双击 HTML 文件(file 协议),发送跨域请求报错 “A***ess to fetch at 'http://localhost:8081' from origin 'null' has been blocked”;

  • 原因:file 协议的 origin 是 “null”,后端allowedOrigins没有配置 “null”,且浏览器对 file 协议的跨域限制更严格;

  • 解决方案:必须用 HTTP 协议访问前端页面(如http://localhost:8080),不能用 file 协议。

五、生产环境跨域配置建议(安全第一)

        在开发环境可以用localhost测试,但生产环境必须严格配置,避免安全风险:

  1. 禁止用 allowedOrigins ("*"):必须指定具体的前端域名(如"https://www.zh.***"),避免恶意网站跨域请求;

  2. 限制 allowedMethods:只允许业务需要的方法(如查询用 GET,提交用 POST,禁止 PUT/DELETE 暴露给前端);

  3. allowedHeaders 按需配置:不建议用 “*”,只允许必要的头(如"Content-Type", "Authorization");

  4. maxAge 合理设置:建议设 3600 秒(1 小时),减少预检请求次数,提升性能;

  5. 配合 HTTPS:生产环境必须用 HTTPS,避免跨域请求中的数据被窃取;

  6. 对敏感接口额外验证:如/admin/**接口,除了 CORS,还要加 Token 验证,双重保障。

六、总结

        Spring Boot 跨域解决方案的核心是通过 CORS 机制,让后端告诉浏览器 “允许哪个源的请求”。我们需要根据项目场景选择合适的配置方式:

  • 简单场景(单个接口 / Controller):用@CrossOrigin注解,快速生效;

  • 复杂场景(多接口 / 生产环境):用全局 CORS 配置(WebMv***onfigurer),统一管理,安全可控。

        记住:跨域问题的本质是浏览器的安全限制,所有配置都围绕 “如何让浏览器认为这个跨域请求是安全的” 展开。掌握本文的实战代码和避坑指南,就能轻松应对前后端分离架构中的跨域问题。

转载请说明出处内容投诉
CSS教程网 » Spring Boot 跨域解决方案详解:从原理到实战(初学者友好版)

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买