一、什么是跨域
跨域 = 浏览器的 “同源策略” 限制
当前端页面所在的源与请求接口的源不一致时,浏览器会判定为跨域。
这里的 源(Origin) 由三部分组成:
协议 + 域名 / 主机 + 端口
只要任意一项不同,就是跨域。
举例判断
页面地址:http://localhost:8080/index.html
| 请求地址 | 是否跨域 | 原因 |
|---|---|---|
| http://localhost:8080/api | 否 | 完全同源 |
| https://localhost:8080/api | 是 | 协议不同(http/https) |
| http://127.0.0.1:8080/api | 是 | 主机不同 |
| http://localhost:8081/api | 是 | 端口不同 |
| api.xxx.com/api | 是 | 域名不同 |
一句话:浏览器认为:不是同一个 “网站”,就不能随便拿数据。
二、跨域是谁造成的?为什么会有?
1. 谁造成的?
完全是浏览器造成的,和服务器、网络、Java 代码无关。
- 服务器收到请求了
- 业务逻辑执行了
- 数据库改了
- 响应也返回了
浏览器:拦截响应 → 不给 JS → 控制台报错
给个一个真实的危险场景:
- 你登录了网上银行
bank.com,浏览器存了你的登录 Cookie。 - 你手贱点了一个恶意网站
hack.com。 - 恶意网站的 JS 偷偷给
bank.com/transfer发了一个转账请求,把你的钱转到黑客账户。
如果没有跨域限制:
- 银行服务器会收到请求
- 会执行转账逻辑
- 会扣你的钱
- 会返回「转账成功」的响应
- 恶意网站的 JS 能拿到这个响应,知道钱转成了
有了跨域限制:
- 银行服务器还是收到了请求
- 还是执行了转账
- 还是扣了你的钱
- 还是返回了「成功」的响应
- 但是!浏览器发现:这个请求是
hack.com发的,不是bank.com发的! - 浏览器直接把「成功」的响应扣下来,不给
hack.com的 JS,还在控制台报错。
注意:钱还是被扣了! 跨域只能防止黑客拿到你的数据,不能防止黑客发请求。这就是为什么还要有 CSRF Token 等防护。
那问题来了:钱已经被扣了,怎么解决?
核心思路:让服务器能识别出「这个请求是正常用户发的,还是恶意网站伪造的」,从根源上拒绝伪造的请求。这就是 CSRF Token(跨站请求伪造防护令牌) 要解决的问题。
CSRF Token 是什么?原理是什么?
- 核心定义
CSRF Token 是服务端生成的、随机、唯一、不可预测的令牌,绑定在用户的会话中,只有「正常访问银行页面的用户」能拿到,恶意网站完全拿不到。
- 工作原理(用银行转账举例)
正常用户的流程(合法请求)
-
用户登录
bank.com,服务端生成一个随机的CSRF Token=abc123xyz,存在用户的 Session 中 -
服务端把这个 Token嵌入到转账页面的 HTML 里(比如隐藏表单、请求头)
<form action="/transfer" method="post"> <input type="hidden" name="csrfToken" value="abc123xyz"> <input type="text" name="toAccount"> <input type="submit" value="转账"> </form> -
用户点击「转账」时,浏览器会把
csrfToken=abc123xyz一起发给银行服务器 -
服务器校验:请求里的 Token 和 Session 里存的 Token 完全一致 → 验证通过,执行转账
恶意网站的攻击流程(伪造请求)
-
用户打开
hack.com,恶意网站想发起转账请求 -
但
hack.com是跨站,完全无法读取bank.com页面里的 CSRF Token(同源策略限制) -
恶意网站发起请求时,只能构造转账参数,带不上正确的 CSRF Token
-
银行服务器收到请求,校验 Token:请求里没有 Token/Token 错误 → 直接拒绝,转账失败!
-
核心逻辑
CSRF 攻击的本质是「冒充用户发请求,但拿不到用户页面里的敏感信息」。CSRF Token 就是利用这一点:把「请求的合法性」和「用户是否正常访问页面」绑定,只有正常访问页面的用户能拿到 Token,恶意网站拿不到,自然无法构造合法请求。
2. 为什么浏览器要这么做?(安全本质)
为了防止 CSRF 攻击(跨站请求伪造) :
你登录了银行网站 bank.com,浏览器保存了它的 Cookie。你打开恶意网站 hack.com。恶意网站 JS 发送请求:
GET https://bank.com/user/info
浏览器会自动带上 bank.com 的 Cookie。
如果没有跨域限制:恶意 JS 直接拿到你的账户、余额、交易记录。
同源策略 = 浏览器保护用户 Cookie 不被第三方网站盗用。
三、跨域会带来什么影响?
1. 前端表现
-
控制台报错:
Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy -
请求状态码可能是 200,但拿不到数据
-
页面无法渲染、接口调用失败
2. 真实网络行为
- 请求成功到达后端
- 后端正常处理
- 响应成功回到浏览器
- 浏览器拦截数据,不交给 JS
3. 附带影响
- 跨域时 Cookie 默认不携带
- 自定义请求头(如 Authorization、Token)会被拦截
- PUT/DELETE/JSON 请求会触发 OPTIONS 预检
四、通用解决方案(原理级)
方案 1:CORS(跨域资源共享)
浏览器与服务器通过 HTTP 头协商:
- 请求头:
Origin标明来源 - 响应头:
Access-Control-Allow-*标明允许谁访问
浏览器检查通过 → 放行数据。
方案 2:Nginx 反向代理
让前端和接口看起来同源,浏览器就不限制。
方案 3:网关统一处理(Spring Cloud Gateway)
在网关层统一加 CORS 头,后端服务无感。
方案 4:JSONP(淘汰)
只支持 GET,利用 <script> 标签无跨域限制,不安全、不推荐。
五、生产级解决方案
方案 1:Nginx 反向代理(最推荐、最安全、生产首选)
原理
前端访问:https://www.xxx.com/api/xxxNginx 把 /api/** 转发到后端服务。
对浏览器来说:始终同源,不存在跨域。
Nginx 配置
server {
listen 443 ssl;
server_name www.xxx.com;
# 前端静态资源
location / {
root /dist;
index index.html;
try_files $uri $uri/ /index.html;
}
# 后端接口代理
location /api/ {
proxy_pass http://backend-server:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
优点
- 彻底消灭跨域
- 安全、统一入口
- 适合微服务、前后端分离
- 金融 / 银行项目标准方案
方案 2:Spring Boot 全局 CORS 配置(可上生产)
适用场景
- 内部系统
- 对外 API 服务
- 没有 Nginx 统一入口
生产级代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
import java.util.Collections;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 生产环境必须写真实前端域名,严禁 *
config.setAllowedOrigins(Arrays.asList(
"https://www.xxx.com",
"https://admin.xxx.com"
));
// 允许携带 Cookie
config.setAllowCredentials(true);
// 允许所有请求头
config.addAllowedHeader("*");
// 允许所有方法
config.addAllowedMethod("*");
// 暴露自定义响应头(前端可读取)
config.addExposedHeader("Authorization");
// 预检缓存 1 小时,减少 OPTIONS 请求
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
生产严禁事项
AllowedOrigin绝对不能用*- 必须开启
AllowCredentials: true才能带 Cookie - 暴露头必须显式声明,否则前端读不到
方案 3:Spring Cloud Gateway 统一跨域(微服务标准)
@Configuration
public class GatewayCorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("https://www.xxx.com"));
config.setAllowCredentials(true);
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
六、前端配合(Axios)
如果你要跨域携带 Cookie / Token:
axios.create({
withCredentials: true, // 跨域带 Cookie
headers: {
'Content-Type': 'application/json'
}
})
七、总结
- 跨域是浏览器的同源策略限制,不是服务器限制。
- 请求能发出去、后端能执行,只是响应被浏览器拦截。
- 目的是防止第三方网站盗用你的 Cookie 做 CSRF。
- 生产最优解:Nginx 反向代理,无跨域、最安全。
- 次优解:后端配置 CORS,严格限制来源域名。