跨域问题详解:原因与解决方案
在 Web 开发中,跨域是前端与后端交互时常见的问题,其根源是浏览器的同源策略。本文将详细解释跨域的定义、产生原因,并提供多种解决方案,包括 Nginx 代理、JSONP 和后端配置等。
什么是跨域?
同源策略
浏览器的同源策略(Same-Origin Policy)是一种安全机制,限制不同源的网页之间的交互。同源指的是 “协议、域名、端口” 三者完全相同:
| URL 1 | URL 2 | 是否同源 | 原因 |
|---|---|---|---|
http://example.com |
http://example.com |
是 | 协议、域名、端口均相同 |
http://example.com |
https://example.com |
否 | 协议不同(http vs https) |
http://example.com |
http://api.example.com |
否 | 域名不同(主域 vs 子域) |
http://example.com:8080 |
http://example.com:8081 |
否 | 端口不同(8080 vs 8081) |
跨域的定义
当一个请求的 “协议、域名、端口” 与当前页面的源不一致时,该请求就是跨域请求。同源策略会阻止跨域请求的成功响应(如 AJAX 请求被拦截),但允许某些标签(如 <script>、<img>)的跨域加载。
跨域解决方案
方案一:Nginx 代理(推荐)
通过 Nginx 作为中间代理,将前端的跨域请求转发到后端,使浏览器认为请求是同源的,从而绕过同源策略。
配置示例:
1 | server { |
原理:
- 前端请求
http://frontend.com/api/user(同源),Nginx 将其转发到http://backend.com:8080/user; - 后端响应通过 Nginx 返回给前端,浏览器认为是同源请求,不拦截响应。
方案二:JSONP(仅支持 GET 请求)
JSONP 利用 <script> 标签不受同源策略限制的特性,通过动态创建 <script> 标签加载跨域资源,实现数据传递。
实现步骤:
前端定义回调函数:
1
2
3
4
5
6
7
8
9// 回调函数,用于接收跨域数据
function handleResponse(data) {
console.log("跨域数据:", data);
}
// 动态创建 <script> 标签,请求跨域接口
const script = document.createElement('script');
script.src = 'http://backend.com/data?callback=handleResponse'; // 传递回调函数名
document.body.appendChild(script);后端返回回调函数调用:
后端接收callback参数,返回一段 JavaScript 代码,调用前端定义的回调函数并传入数据:1
2
3
4
5
6
7
public String getData( String callback) {
// 模拟数据
String data = "{\"name\": \"张三\", \"age\": 20}";
// 返回格式:callbackFunction(data)
return callback + "(" + data + ")";
}后端返回结果示例:
1
handleResponse({"name": "张三", "age": 20})
缺点:
- 仅支持 GET 请求(
<script>标签只能发起 GET 请求); - 存在安全风险(可能引入恶意代码)。
方案三:后端配置 CORS(跨域资源共享)
CORS(Cross-Origin Resource Sharing)是 W3C 标准,允许服务器通过响应头声明允许哪些跨域请求,是目前最主流的跨域解决方案。
实现方式:
通过过滤器(Filter)或拦截器(Interceptor)在响应头中添加 CORS 相关配置。
(1)Java Web 过滤器实现:
1 | import javax.servlet.*; |
(2)Spring Boot 注解实现:
使用 @CrossOrigin 注解更简洁地配置 CORS:
1 | import org.springframework.web.bind.annotation.CrossOrigin; |
原理:
- 简单请求(如 GET、POST 且 Content-Type 为
application/x-www-form-urlencoded):直接发送请求,服务器通过Access-Control-Allow-Origin允许跨域; - 预检请求(如 PUT、DELETE 或自定义头):浏览器先发送
OPTIONS请求检查服务器是否允许跨域,通过后再发送实际请求。
各方案对比与选择
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Nginx 代理 | 支持所有请求方法,安全性高,前端无需修改 | 需要配置 Nginx | 生产环境,前后端分离架构 |
| JSONP | 兼容性好(支持老式浏览器) | 仅支持 GET,有安全风险 | 遗留系统,简单跨域需求 |
| CORS | 标准方案,支持所有请求方法和自定义头 | 部分老式浏览器不支持(如 IE 10 以下) | 现代 Web 应用,前后端分离 |
注意事项
- 安全性:
- 生产环境中,
Access-Control-Allow-Origin不应设为*,需指定具体域名,避免恶意网站跨域访问; - 启用
Access-Control-Allow-Credentials时,Access-Control-Allow-Origin必须是具体域名,不能为*。
- 生产环境中,
- 预检请求:
对于非简单请求(如带自定义头的 POST),浏览器会先发送OPTIONS预检请求,后端需正确响应(如返回 200 状态码),否则实际请求会被拦截。 - Cookie 跨域:
若需要跨域传递 Cookie,需同时满足:- 前端请求开启
withCredentials: true(如axios.defaults.withCredentials = true); - 后端设置
Access-Control-Allow-Credentials: true且Access-Control-Allow-Origin为具体域名。
- 前端请求开启