0%

跨域

跨域问题详解:原因与解决方案

在 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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> 标签加载跨域资源,实现数据传递。

实现步骤:
  1. 前端定义回调函数

    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);
  2. 后端返回回调函数调用
    后端接收 callback 参数,返回一段 JavaScript 代码,调用前端定义的回调函数并传入数据:

    1
    2
    3
    4
    5
    6
    7
    @GetMapping("/data")
    public String getData(@RequestParam 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter("/*") // 拦截所有请求
public class CorsFilter implements Filter {
@Override
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
// 允许来自 http://frontend.com 的跨域请求
@CrossOrigin(origins = "http://frontend.com", allowCredentials = "true")
public class UserController {
@GetMapping("/user")
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 应用,前后端分离

注意事项

  1. 安全性
    • 生产环境中,Access-Control-Allow-Origin 不应设为 *,需指定具体域名,避免恶意网站跨域访问;
    • 启用 Access-Control-Allow-Credentials 时,Access-Control-Allow-Origin 必须是具体域名,不能为 *
  2. 预检请求
    对于非简单请求(如带自定义头的 POST),浏览器会先发送 OPTIONS 预检请求,后端需正确响应(如返回 200 状态码),否则实际请求会被拦截。
  3. Cookie 跨域
    若需要跨域传递 Cookie,需同时满足:
    • 前端请求开启 withCredentials: true(如 axios.defaults.withCredentials = true);
    • 后端设置 Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin 为具体域名。

欢迎关注我的其它发布渠道