跨域
一、什么是跨域?
跨域问题是由浏览器的 同源策略 引起的。同源策略是浏览器一个重要的安全机制,它用于限制一个源的文档或脚本如何与另一个源的资源进行交互。
同源的定义 :如果两个 URL 的协议(Protocol)、域名(Host)、端口(Port) 都完全相同,则这两个 URL 是同源的。
当前页面URL | 目标URL | 是否同源 | 原因 |
---|---|---|---|
https://www.example.com/index.html |
https://www.example.com/api/user |
是 | 协议、域名、端口均相同 |
https://www.example.com/index.html |
http://www.example.com/api/user |
否 | 协议不同 (https vs http) |
https://www.example.com/index.html |
https://api.example.com/user |
否 | 域名不同 (www vs api) |
https://www.example.com:8080/index.html |
https://www.example.com/api/user |
否 | 端口不同 (8080 vs 默认443) |
只要有一个不同,就是跨域 。浏览器会阻止跨域的 AJAX请求 返回的数据被页面 JavaScript 获取,但对于像 <img>
, <script>
, <link>
, <iframe>
等标签的资源引用,浏览器是允许的。
这里讲讲 AJAX请求 :A synchronous J avaScript A nd XML
AJAX 是 A synchronous J avaScript A nd XML 的缩写。
- Asynchronous(异步): 这是 AJAX 的灵魂。当你向服务器发送请求后,JavaScript 代码不会傻傻地等待服务器响应,而是可以继续执行其他任务。当服务器响应返回时,JavaScript 再回过头来处理这个响应。这使得用户体验非常流畅,不会出现页面"卡死"的情况。
- JavaScript: 整个过程的"大脑"。它负责发送请求、监听响应、并最终用返回的数据更新页面。
- XML : 最初,AJAX 技术中服务器返回的数据格式通常是 XML。但现在更常用的是 JSON(JavaScript Object Notation)格式,因为它更轻量、更容易被 JavaScript 解析。所以,虽然名字里有 XML,但现在 AJAX 请求的数据格式可以是 JSON、XML、纯文本或 HTML。
AJAX 是一种用于创建快速、动态网页的技术。它允许网页在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容。
可以把它想象成点餐的过程:
- 传统方式(无 AJAX): 每点一道菜,你就把整本菜单(整个页面)送回厨房(服务器),厨房做好一道菜后,把一本全新的菜单(整个新页面)连同这道菜一起给你。效率极低。
- AJAX 方式: 你有一个智能服务员(AJAX)。你点菜(触发事件),服务员悄悄地去厨房(服务器)下单,厨房只做好这一道菜(数据),服务员只把这道菜端给你(局部更新页面)。你面前的餐桌(页面其他部分)完全不受影响。
二、为什么需要同源策略?(安全原因)
没有同源策略会非常危险。想象一下这个场景:
- 你登录了网上银行,网站
https://my-bank.com
将你的身份认证信息(如 Cookie)保存在你的浏览器中。 - 然后,你不小心访问了一个恶意网站
https://evil-site.com
。 - 这个恶意网站的 JavaScript 尝试向
https://my-bank.com/transfer
发起一个 AJAX 请求,要求转账给攻击者。 - 浏览器会自动带上你在
my-bank.com
的 Cookie。 - 如果没有同源策略,恶意网站就能拿到银行网站的响应,甚至完成转账操作。
同源策略有效地防止了这种"跨站请求伪造"等攻击,保护了用户的数据安全。
三、跨域问题再现
典型开发环境:
- 后端 Java 服务 :运行在
http://localhost:8080
- 前端 Vue 开发服务器 :运行在
http://localhost:3000
或http://localhost:5173
(Vite)
当 Vue 页面中的 Axios 尝试请求 http://localhost:8080/api/users
时,由于端口不同,浏览器会触发同源策略,阻止请求,并在控制台报错:Access to XMLHttpRequest at 'http://localhost:8080/api/users' from origin 'http://localhost:3000' has been blocked by CORS policy
。
四、解决方案一:后端解决(配置 CORS)【主流方案】
这是最规范、最生产环境友好的方案。核心思想是让 Java 后端在响应头中添加允许跨域的字段。
1. 使用 @CrossOrigin
注解(简单方便)
在 Spring Boot 的 Controller 类或方法上添加注解,适用于快速测试或粒度控制。
java
@RestController
@RequestMapping("/api")
// 在类上使用,对该控制器所有方法生效
@CrossOrigin(origins = "http://localhost:3000") // 允许特定源
public class UserController {
@GetMapping("/users")
// 也可以在方法上使用,更细粒度控制
@CrossOrigin(origins = "http://localhost:3000")
public List<User> getUsers() {
// ... 业务逻辑
return userService.findAll();
}
@PostMapping("/users")
// 允许所有源(生产环境慎用)
@CrossOrigin(origins = "*")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
优缺点:
- 优:配置简单,一目了然。
- 缺:多个 Controller 需要重复配置,不够集中;无法处理复杂需求(如允许自定义头、缓存预检请求等)。
2. 实现 WebMvcConfigurer
接口(推荐,全局配置)
通过 Java 配置类进行全局配置,这是最常用和灵活的方式。
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 配置针对哪些API路径
.allowedOrigins("http://localhost:3000", "https://www.your-app.com") // 允许的源
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的HTTP方法
.allowedHeaders("*") // 允许所有的请求头
.allowCredentials(true) // 允许发送Cookie等凭证
.maxAge(3600); // 预检请求的缓存时间(秒),减少OPTIONS请求
}
}
关键配置解释:
allowedOrigins
:生产环境应替换为 Vue 应用的实际部署域名,而不是*
。allowCredentials(true)
:如果前端请求需要携带 Cookie 或 Authorization 头,此项必须为true
,且allowedOrigins
不能为*
。maxAge
:设置预检请求结果的缓存时间,提升性能。
3. 使用 CorsFilter(更底层,功能强大)
对于非 Spring MVC 项目或需要更精细控制的情况,可以配置自定义 Filter。
java
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
@Configuration
public class CustomCorsFilter {
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 明确设置允许的源,而不是使用"*"
config.setAllowedOrigins(Arrays.asList("http://localhost:3000", "https://www.your-app.com"));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(Arrays.asList("*")); // 或明确指定:"Authorization", "Content-Type"
// 针对所有路径应用此CORS配置
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(0); // 设置过滤器最高优先级
return bean;
}
}
五、解决方案二:前端解决(开发环境代理)
这个方案主要在开发阶段使用,利用 Vue CLI 或 Vite 内置的代理功能,将 API 请求"转发"到后端服务器。因为代理服务器和 Vue 应用本身是同源的,所以浏览器不会报跨域错误。
1. Vue CLI (Webpack Dev Server) 配置
在项目根目录下的 vue.config.js
文件中配置:
java
// vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 3000,
proxy: {
// 代理所有以 '/api' 开头的请求
'/api': {
target: 'http://localhost:8080', // 你的Java后端地址
changeOrigin: true, // 改变请求头中的Origin为目标URL
pathRewrite: {
'^/api': '' // 重写路径,去掉 '/api' 前缀
// 例如:前端请求 /api/users -> 代理到 http://localhost:8080/users
}
}
}
}
})
前端 Vue 组件中的请求示例:
java
// 在Vue组件中,直接请求相对路径 '/api/...'
import axios from 'axios';
export default {
methods: {
async fetchUsers() {
try {
// 开发阶段:请求被代理到 -> http://localhost:8080/api/users
// 生产阶段:请求直接发往 -> https://your-api-domain.com/api/users (需要配置生产环境BASE_URL)
const response = await axios.get('/api/users');
this.users = response.data;
} catch (error) {
console.error('获取用户数据失败:', error);
}
}
}
}
2. Vite 配置
在 vite.config.js
或 vite.config.ts
中配置:
java
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
六、生产环境部署策略
部署方式 | 跨域解决方案 | 说明 |
---|---|---|
前后端分离部署 | Java 后端配置 CORS | 前端(Vue打包后的静态文件)部署在 Nginx/CDN(域A),后端Java应用部署在另一个服务器(域B)。这是最经典的场景,必须由后端配置CORS。 |
前后端同域部署 | 无需处理跨域 | 将Vue打包后的 dist 目录内容放在Java项目的 static 目录下,或使用Nginx将API请求反向代理到后端,静态资源直接服务。由于同源,自然没有跨域问题。 |