在 Web 开发中,跨域问题(Cross-Origin)是一个常见但又容易让人困惑的话题。尤其是在前后端分离的开发模式下,前端页面和后端接口往往运行在不同的域名或端口,导致浏览器报出类似“No ‘Access-Control-Allow-Origin’ header”的错误。那么,跨域到底是什么?为什么它是由浏览器引起的,却需要后端来解决?本文将从基础概念入手,逐步解答这些疑问,并分享在 Spring Boot 中处理跨域的实用方法。
一、什么是跨域?
跨域的根源在于浏览器的同源策略(Same-Origin Policy)。简单来说,同源策略要求两个 URL 的协议、域名和端口号完全一致,只有这样,浏览器才会认为它们是“同源”的,允许它们自由交互。
同源的例子
http://example.com:80/page1
和http://example.com:80/page2
→ 同源(协议、域名、端口都相同)。http://example.com:80
和https://example.com:80
→ 不同源(协议不同)。http://example.com:80
和http://sub.example.com:80
→ 不同源(域名不同)。http://example.com:80
和http://example.com:8080
→ 不同源(端口不同)。
当一个网页试图通过 JavaScript(如 fetch
或 XMLHttpRequest
)访问不同源的资源时,就发生了跨域请求。例如,前端运行在 http://localhost:3000
,后端接口在 http://localhost:8080
,由于端口不同,浏览器会限制这种请求。
二、为什么会有跨域限制?
浏览器的同源策略是为了保护用户安全。假设没有这个限制:
- 你访问了一个恶意网站
http://evil.com
。 - 该网站通过 JavaScript 悄悄向你的银行网站
http://bank.com
发送请求。 - 如果你已登录银行网站(浏览器存有 Cookie),恶意网站可能窃取你的账户信息。
同源策略通过限制不同源之间的交互,避免了这种安全风险。但在现代 Web 开发中,前后端分离是常态,跨域请求变得不可避免,因此需要一种机制来安全地放宽限制——这就是 CORS(跨源资源共享)。
三、跨域问题的表现
当你尝试跨域访问一个接口时,浏览器会在控制台抛出错误:
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
:允许的请求方法,如GET
、POST
。Access-Control-Allow-Headers
:允许的请求头。
请求流程
- 前端(
http://localhost:3000
)向后端(http://localhost:8080
)发送请求。 - 浏览器检测到跨域,检查服务器的响应。
- 后端返回数据,并在响应头中添加:
Access-Control-Allow-Origin: http://localhost:3000
- 浏览器看到这个头信息,确认服务器授权,于是允许前端访问数据。
如果没有这个头,或者来源不匹配,浏览器会继续拦截。
为什么浏览器信任后端?
- 服务器是资源主人:它有权决定谁能访问自己的数据。
- 浏览器是守门员:它只负责执行策略,根据服务器的声明放行或拦截。
- 安全有保障:即使服务器允许所有来源(
*
),涉及用户凭证的请求还需额外配置(如Access-Control-Allow-Credentials
),防止滥用。
换句话说,CORS 是服务器和浏览器之间的“协议”,后端通过头信息授权,浏览器遵从执行。
五、在 Spring Boot 中解决跨域
在实际开发中,我常用的框架是 Spring Boot,以下是几种解决跨域的方法:
1. 使用 @CrossOrigin
注解(局部控制)
在控制器或方法上添加注解,适合特定接口:
@RestController
@RequestMapping("/api")
public class MyController {
@CrossOrigin(origins = "http://localhost:3000")
@GetMapping("/data")
public String getData() {
return "Hello from Spring Boot!";
}
}
2. 全局配置(推荐)
通过 WebMvcConfigurer
设置全局跨域规则:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
/**
:对所有接口生效。allowCredentials(true)
:支持带 Cookie 的请求,但allowedOrigins
不能为*
。
3. 自定义 Filter
更灵活的方式,通过过滤器设置响应头:
@Component
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
chain.doFilter(req, res);
}
}
注意事项
- 开发 vs 生产:开发时可以用
*
简化,生产环境应指定具体来源。 - 预检请求:复杂请求会触发 OPTIONS 请求,后端需正确处理。
- 代理替代:开发时可以用前端代理(如 Webpack)绕过跨域,但生产仍需后端配置。
六、总结
跨域问题源于浏览器的同源策略,目的是保护用户安全。通过 CORS,服务器可以主动授权跨域访问,而后端设置生效的原因在于它是资源的所有者,浏览器只是执行者。在 Spring Boot 中,我们可以通过注解、全局配置或 Filter 轻松解决跨域问题。
理解跨域的关键是弄清楚安全与功能的平衡。希望这篇文章能帮你在开发中少走弯路!如果有其他问题,欢迎留言讨论。