在 Web 开发中,跨域问题(Cross-Origin)是一个常见但又容易让人困惑的话题。尤其是在前后端分离的开发模式下,前端页面和后端接口往往运行在不同的域名或端口,导致浏览器报出类似“No ‘Access-Control-Allow-Origin’ header”的错误。那么,跨域到底是什么?为什么它是由浏览器引起的,却需要后端来解决?本文将从基础概念入手,逐步解答这些疑问,并分享在 Spring Boot 中处理跨域的实用方法。

一、什么是跨域?

跨域的根源在于浏览器的同源策略(Same-Origin Policy)。简单来说,同源策略要求两个 URL 的协议域名端口号完全一致,只有这样,浏览器才会认为它们是“同源”的,允许它们自由交互。

同源的例子

  • http://example.com:80/page1http://example.com:80/page2
    → 同源(协议、域名、端口都相同)。
  • http://example.com:80https://example.com:80
    → 不同源(协议不同)。
  • http://example.com:80http://sub.example.com:80
    → 不同源(域名不同)。
  • http://example.com:80http://example.com:8080
    → 不同源(端口不同)。

当一个网页试图通过 JavaScript(如 fetchXMLHttpRequest)访问不同源的资源时,就发生了跨域请求。例如,前端运行在 http://localhost:3000,后端接口在 http://localhost:8080,由于端口不同,浏览器会限制这种请求。

二、为什么会有跨域限制?

浏览器的同源策略是为了保护用户安全。假设没有这个限制:

  1. 你访问了一个恶意网站 http://evil.com
  2. 该网站通过 JavaScript 悄悄向你的银行网站 http://bank.com 发送请求。
  3. 如果你已登录银行网站(浏览器存有 Cookie),恶意网站可能窃取你的账户信息。

同源策略通过限制不同源之间的交互,避免了这种安全风险。但在现代 Web 开发中,前后端分离是常态,跨域请求变得不可避免,因此需要一种机制来安全地放宽限制——这就是 CORS(跨源资源共享)。

三、跨域问题的表现

当你尝试跨域访问一个接口时,浏览器会在控制台抛出错误:

  1. Access to fetch at 'http://localhost:8080/api/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

这表示服务器没有在响应中声明允许跨域访问,浏览器因此拦截了数据。

四、为什么后端设置就能解决问题?

既然跨域是浏览器的安全策略,为什么通过后端配置就能解决?答案在于 CORS 的工作机制。

CORS 的核心

CORS 是一种标准,允许服务器通过响应头告诉浏览器哪些来源可以访问资源。常见的头信息包括:

  • Access-Control-Allow-Origin:指定允许的来源,例如 http://localhost:3000
  • Access-Control-Allow-Methods:允许的请求方法,如 GETPOST
  • Access-Control-Allow-Headers:允许的请求头。

请求流程

  1. 前端(http://localhost:3000)向后端(http://localhost:8080)发送请求。
  2. 浏览器检测到跨域,检查服务器的响应。
  3. 后端返回数据,并在响应头中添加:
    1. Access-Control-Allow-Origin: http://localhost:3000
  4. 浏览器看到这个头信息,确认服务器授权,于是允许前端访问数据。

如果没有这个头,或者来源不匹配,浏览器会继续拦截。

为什么浏览器信任后端?

  • 服务器是资源主人:它有权决定谁能访问自己的数据。
  • 浏览器是守门员:它只负责执行策略,根据服务器的声明放行或拦截。
  • 安全有保障:即使服务器允许所有来源(*),涉及用户凭证的请求还需额外配置(如 Access-Control-Allow-Credentials),防止滥用。

换句话说,CORS 是服务器和浏览器之间的“协议”,后端通过头信息授权,浏览器遵从执行。

五、在 Spring Boot 中解决跨域

在实际开发中,我常用的框架是 Spring Boot,以下是几种解决跨域的方法:

1. 使用 @CrossOrigin 注解(局部控制)

在控制器或方法上添加注解,适合特定接口:

  1. @RestController
  2. @RequestMapping("/api")
  3. public class MyController {
  4. @CrossOrigin(origins = "http://localhost:3000")
  5. @GetMapping("/data")
  6. public String getData() {
  7. return "Hello from Spring Boot!";
  8. }
  9. }

2. 全局配置(推荐)

通过 WebMvcConfigurer 设置全局跨域规则:

  1. @Configuration
  2. public class WebConfig implements WebMvcConfigurer {
  3. @Override
  4. public void addCorsMappings(CorsRegistry registry) {
  5. registry.addMapping("/**")
  6. .allowedOrigins("http://localhost:3000")
  7. .allowedMethods("GET", "POST", "PUT", "DELETE")
  8. .allowedHeaders("*")
  9. .allowCredentials(true)
  10. .maxAge(3600);
  11. }
  12. }
  • /**:对所有接口生效。
  • allowCredentials(true):支持带 Cookie 的请求,但 allowedOrigins 不能为 *

3. 自定义 Filter

更灵活的方式,通过过滤器设置响应头:

  1. @Component
  2. public class CorsFilter implements Filter {
  3. @Override
  4. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  5. throws IOException, ServletException {
  6. HttpServletResponse response = (HttpServletResponse) res;
  7. response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
  8. response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  9. chain.doFilter(req, res);
  10. }
  11. }

注意事项

  • 开发 vs 生产:开发时可以用 * 简化,生产环境应指定具体来源。
  • 预检请求:复杂请求会触发 OPTIONS 请求,后端需正确处理。
  • 代理替代:开发时可以用前端代理(如 Webpack)绕过跨域,但生产仍需后端配置。

六、总结

跨域问题源于浏览器的同源策略,目的是保护用户安全。通过 CORS,服务器可以主动授权跨域访问,而后端设置生效的原因在于它是资源的所有者,浏览器只是执行者。在 Spring Boot 中,我们可以通过注解、全局配置或 Filter 轻松解决跨域问题。

理解跨域的关键是弄清楚安全与功能的平衡。希望这篇文章能帮你在开发中少走弯路!如果有其他问题,欢迎留言讨论。