跨域
跨域问题详解:原因与解决方案
在 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 作为中间代理,将前端的跨域请求转发到后端,使浏览器认为请求是同源的,从而绕过同源策略。
配置示例:
server {
listen 80;
server_name frontend.com; # 前端域名
# 转发 API 请求到后端服务器
location /api/ {
proxy_pass http://backend.com:8080/; # 后端接口地址
# 允许跨域的响应头配置
add_header 'Access-Control-Allow-Origin' '*'; # 允许所有源(生产环境建议指定具体域名)
add_header 'Access-Control-Allow-Credentials' 'true'; # 允许携带 Cookie
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; # 允许的请求方法
add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept, Token'; # 允许的请求头
}
}
原理:
- 前端请求
http://frontend.com/api/user(同源),Nginx 将其转发到http://backend.com:8080/user; - 后端响应通过 Nginx 返回给前端,浏览器认为是同源请求,不拦截响应。
方案二:JSONP(仅支持 GET 请求)
JSONP 利用 <script> 标签不受同源策略限制的特性,通过动态创建 <script> 标签加载跨域资源,实现数据传递。
实现步骤:
前端定义回调函数:
// 回调函数,用于接收跨域数据 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 代码,调用前端定义的回调函数并传入数据:public String getData( String callback) { // 模拟数据 String data = "{\"name\": \"张三\", \"age\": 20}"; // 返回格式:callbackFunction(data) return callback + "(" + data + ")"; }后端返回结果示例:
handleResponse({"name": "张三", "age": 20})
缺点:
- 仅支持 GET 请求(
<script>标签只能发起 GET 请求); - 存在安全风险(可能引入恶意代码)。
方案三:后端配置 CORS(跨域资源共享)
CORS(Cross-Origin Resource Sharing)是 W3C 标准,允许服务器通过响应头声明允许哪些跨域请求,是目前最主流的跨域解决方案。
实现方式:
通过过滤器(Filter)或拦截器(Interceptor)在响应头中添加 CORS 相关配置。
(1)Java Web 过滤器实现:
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// 拦截所有请求
public class CorsFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 允许的源(* 表示所有源,生产环境建议指定具体域名,如 http://frontend.com)
httpResponse.setHeader("Access-Control-Allow-Origin", "*");
// 允许携带 Cookie(此时 Access-Control-Allow-Origin 不能为 *,需指定具体域名)
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
// 允许的请求方法
httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
// 允许的请求头(如自定义的 Token 头)
httpResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Token");
// 预检请求的有效期(秒),有效期内不再发送预检请求
httpResponse.setHeader("Access-Control-Max-Age", "3600");
chain.doFilter(request, response);
}
}
(2)Spring Boot 注解实现:
使用 @CrossOrigin 注解更简洁地配置 CORS:
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
// 允许来自 http://frontend.com 的跨域请求
public class UserController {
public String getUser() {
return "{'name': '张三'}";
}
}
原理:
- 简单请求(如 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为具体域名。
- 前端请求开启