一、跨域问题概述
(一)什么是跨域问题
- 定义
- 跨域问题源于浏览器的 同源策略(Same-Origin Policy)。同源策略是一种重要的安全机制,它限制了来自不同源(Origin)的文档或脚本之间的交互能力。具体来说,如果一个网页的资源(如HTML、JavaScript、CSS等)来自一个源(例如
http://example.com
),那么它只能与来自同一源的资源进行交互。如果尝试访问来自另一个源(例如http://another-example.com
)的资源,就会触发跨域问题。 - 源的定义包括:协议(
http
、https
)、域名(example.com
、sub.example.com
)、端口号(80
、8080
)。只要这三者中有一个不同,就被视为不同源。
- 背景
- 在现代Web开发中,前后端分离架构非常常见。前端项目通常运行在本地开发服务器(如
http://localhost:8080
),而后端服务可能运行在另一个端口(如http://localhost:8789
)或完全不同的域名上。在这种情况下,前端代码尝试向后端服务发送请求时,就会遇到跨域问题。
- 示例
-
假设前端项目运行在
http://localhost:8080
,后端服务运行在http://localhost:8789
。当前端代码中使用axios
或其他HTTP客户端向http://localhost:8789/api/data
发送请求时,浏览器会阻止该请求,并在控制台中报错:Access to XMLHttpRequest at 'http://localhost:8789/api/data' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
-
这是因为后端服务没有明确告诉浏览器它允许来自
http://localhost:8080
的请求。
(二)跨域问题的表现
- 常见的错误信息
Access to XMLHttpRequest at 'http://example.com/api/data' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
:表示后端没有返回Access-Control-Allow-Origin
头,浏览器拒绝了请求。Access to fetch at 'http://example.com/api/data' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
:表示预检请求(OPTIONS请求)失败。Access to XMLHttpRequest at 'http://example.com/api/data' from origin 'http://localhost:8080' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:8080, *', but only one is allowed.
:表示Access-Control-Allow-Origin
头的值不合法,不能同时包含多个值。
- 影响
- 跨域问题会导致前端无法正常获取后端数据,影响页面的渲染和功能实现。
- 如果不解决跨域问题,前端项目在开发阶段和生产环境中都无法正常工作。
(三)跨域问题的解决思路
- 后端解决
- 后端可以通过设置响应头,允许特定的前端域名或所有域名访问其资源。这是最常用和推荐的解决方案,因为它将跨域控制逻辑集中在后端,前端不需要做额外的处理。
- 前端解决
- 前端可以通过配置代理,将请求转发到后端服务。这种方法主要适用于开发阶段,生产环境中通常不建议使用前端代理。
- 其他解决方案
- JSONP(仅支持GET请求):通过动态创建
<script>
标签来绕过跨域限制,但这种方法存在安全风险,且功能有限。 - WebSocket:WebSocket协议不受同源策略限制,因此可以用于跨域通信,但仅适用于实时通信场景。
二、后端(Spring Boot)跨域配置
(一)使用 @CrossOrigin
注解
- 基本用法
-
@CrossOrigin
注解是Spring MVC提供的一个简单方式,用于在Controller类或方法上声明允许跨域请求。 -
示例代码:
@RestController public class BasicController {@RequestMapping("/user")@CrossOrigin("http://localhost:5173")public User user() {User user = new User();user.setName("张三");user.setAge(666);return user;} }
-
在这个例子中,
@CrossOrigin("http://localhost:5173")
表示只允许来自http://localhost:5173
的请求访问/user
接口。
- 高级配置
-
@CrossOrigin
注解支持多种配置选项:origins
:允许访问的前端域名,可以是一个字符串或字符串数组。如果设置为*
,表示允许所有域名访问。methods
:允许的HTTP方法,如GET
、POST
、PUT
、DELETE
等。如果未指定,则默认允许所有方法。allowedHeaders
:允许的请求头字段。exposedHeaders
:允许客户端访问的响应头字段。allowCredentials
:是否允许发送Cookie。如果设置为true
,则origins
不能为*
。maxAge
:预检请求(OPTIONS请求)的缓存时间,单位为秒。
-
示例代码:
@RestController public class BasicController {@RequestMapping("/user")@CrossOrigin(origins = "http://localhost:5173", methods = {RequestMethod.GET, RequestMethod.POST}, allowedHeaders = "X-Requested-With", allowCredentials = "true", maxAge = 3600)public User user() {User user = new User();user.setName("张三");user.setAge(666);return user;} }
(二)全局CORS配置
- 通过实现
WebMvcConfigurer
接口
-
如果希望对整个应用的跨域行为进行统一配置,可以实现
WebMvcConfigurer
接口并重写addCorsMappings
方法。 -
示例代码:
@Configuration public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**") // 配置允许跨域的路径,`/**`表示所有路径.allowedOrigins("http://localhost:5173") // 允许的前端域名.allowCredentials(true) // 是否允许发送Cookie.maxAge(3600) // 预检请求的缓存时间,单位为秒.allowedHeaders("*") // 允许的请求头字段.allowedMethods("GET", "POST", "PUT", "DELETE"); // 允许的HTTP方法} }
-
这种方式的优点是配置集中,便于管理和维护。缺点是不够灵活,无法针对特定接口进行特殊配置。
- 配置项说明
addMapping
:指定需要跨域的路径。allowedOrigins
:允许访问的前端域名,可以是一个字符串或字符串数组。如果设置为*
,表示允许所有域名访问。allowCredentials
:是否允许发送Cookie。如果设置为true
,则allowedOrigins
不能为*
。maxAge
:预检请求(OPTIONS请求)的缓存时间,单位为秒。allowedHeaders
:允许的请求头字段。allowedMethods
:允许的HTTP方法。
(三)配置 CorsFilter
- 创建
CorsFilter
-
如果需要更细粒度的跨域控制,可以创建一个
CorsFilter
。通过CorsConfiguration
和UrlBasedCorsConfigurationSource
来配置CORS,并返回CorsFilter
对象。 -
示例代码:
@Configuration public class CorsFilterConfig {@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration config = new CorsConfiguration();config.setAllowCredentials(true); // 是否允许发送Cookieconfig.addAllowedOrigin("http://localhost:5173"); // 允许的前端域名config.addAllowedHeader("*"); // 允许的请求头字段config.addAllowedMethod("*"); // 允许的HTTP方法source.registerCorsConfiguration("/**", config); // 配置允许跨域的路径return new CorsFilter(source);} }
- 配置项说明
CorsConfiguration
:用于配置CORS的具体规则。UrlBasedCorsConfigurationSource
:用于将CORS配置与URL路径关联起来。CorsFilter
:最终生成的过滤器,会在请求到达Controller之前检查跨域规则。
(四)手动设置响应头
- 在Controller中设置
-
在Controller方法中,可以通过
HttpServletResponse
手动添加Access-Control-Allow-Origin
响应头来允许特定的前端域名访问。 -
示例代码:
@RestController public class BasicController {@RequestMapping("/index")public String index(HttpServletResponse response) {response.addHeader("Access-Control-Allow-Origin", "http://localhost:5173");response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");response.addHeader("Access-Control-Allow-Headers", "X-Requested-With");response.addHeader("Access-Control-Allow-Credentials", "true");return "index";} }
- 适用场景
- 这种方式适用于简单的场景,或者需要在特定方法中动态设置跨域规则的情况。但不推荐在大型项目中广泛使用,因为它会增加代码的复杂性和维护成本。
三、前端(Vue)跨域配置
(一)配置代理
- 原理
- 在开发阶段,Vue CLI提供了一个代理功能,可以将前端请求转发到后端服务。这样,浏览器会认为请求是同源的,从而避免跨域问题。
- 代理的原理是通过Node.js的
http-proxy-middleware
中间件实现的。当前端发送请求到代理服务器时,代理服务器会将请求转发到目标后端服务,并将响应返回给前端。
- 配置方法
-
在Vue项目的
vue.config.js
文件中配置代理:module.exports = {devServer: {proxy: {'/api': {target: 'http://localhost:8789', // 后端服务的地址changeOrigin: true, // 是否允许跨域pathRewrite: {'^/api': '' // 重写路径,去掉`/api`前缀}}}} };
-
在这个例子中,当前端发送请求到
http://localhost:8080/api/data
时,Vue CLI的开发服务器会将请求转发到http://localhost:8789/data
。
- 适用场景
- 代理主要适用于开发阶段,用于解决开发环境下的跨域问题。在生产环境中,通常不建议使用前端代理,而是通过后端配置CORS或使用其他方式解决跨域问题。
(二)修改前端请求路径
- 路径调整
-
配置代理后,前端代码中的请求路径需要调整为代理路径。例如,如果代理配置为
/api
,则请求路径应从http://localhost:8789/data
改为/api/data
。 -
示例代码:
axios.get('/api/data').then(response => {console.log(response.data);}).catch(error => {console.error(error);});
- 注意事项
- 确保请求路径与代理配置一致,否则代理不会生效。
- 在生产环境中,前端代码中的请求路径应直接指向后端服务的实际地址,而不是代理路径。
四、测试与验证
(一)启动服务
- 后端服务
- 启动Spring Boot项目,确保后端服务正常运行。可以通过访问后端接口(如
http://localhost:8789/api/data
)来验证后端是否正常响应。
- 前端服务
- 启动Vue项目,运行在本地开发服务器(如
http://localhost:8080
)。可以通过访问前端页面来验证前端是否正常加载。
(二)发起请求
- 测试跨域请求
-
在前端页面中,使用
axios
或其他HTTP客户端向后端接口发送请求:axios.get('/api/data').then(response => {console.log(response.data);}).catch(error => {console.error(error);});
-
观察浏览器控制台的输出:
- 如果配置正确,前端应能成功获取后端数据,并在控制台中打印出响应内容。
- 如果配置错误,浏览器会报错,显示跨域问题的相关错误信息。
- 检查响应头
- 使用开发者工具(如Chrome DevTools)查看响应头,确认后端是否正确返回了CORS相关的头字段,如
Access-Control-Allow-Origin
、Access-Control-Allow-Methods
等。
(三)验证不同场景
- 测试不同HTTP方法
- 分别测试
GET
、POST
、PUT
、DELETE
等HTTP方法,确保后端的跨域配置支持所有需要的方法。
- 测试带Cookie的请求
-
如果需要在请求中携带Cookie(如登录认证),确保
allowCredentials
配置为true
,并且allowedOrigins
不能为*
。 -
示例代码:
axios.get('/api/data', { withCredentials: true }).then(response => {console.log(response.data);}).catch(error => {console.error(error);});
- 测试预检请求(OPTIONS)
- 浏览器在发送某些复杂请求(如带有自定义头的
POST
请求)之前,会先发送一个OPTIONS
预检请求,以确认后端是否允许该请求。确保后端的跨域配置能够正确处理OPTIONS
请求。
五、拓展学习
(一)了解跨域的其他解决方案
- JSONP
-
原理:JSONP(JSON with Padding)是一种古老的跨域解决方案。它的原理是利用
<script>
标签的src
属性不受同源策略限制的特点,通过动态创建<script>
标签来加载一个外部的JavaScript文件,并在加载完成后执行回调函数。 -
示例代码:
function handleResponse(data) {console.log(data); }const script = document.createElement('script'); script.src = 'http://example.com/data?callback=handleResponse'; document.head.appendChild(script);
-
限制:JSONP仅支持
GET
请求,且存在安全风险(如XSS攻击),因此不推荐在现代项目中使用。
- WebSocket
-
原理:WebSocket是一种基于TCP的协议,用于实现客户端和服务器之间的双向通信。WebSocket协议不受同源策略限制,因此可以用于跨域通信。
-
适用场景:适用于需要实时通信的场景,如在线聊天、实时数据推送等。
-
示例代码:
const socket = new WebSocket('ws://example.com/socket'); socket.onmessage = function(event) {console.log(event.data); }; socket.send('Hello, server!');
(二)学习跨域问题的原理
- 同源策略
- 定义:同源策略是一种重要的网络安全机制,用于限制来自不同源的文档或脚本之间的交互能力。它防止恶意网站窃取用户的敏感信息,如Cookie、本地存储等。
- 原理:浏览器会检查请求的源(协议、域名、端口)是否与当前页面的源一致。如果不一致,则会阻止某些操作,如读取DOM、发送AJAX请求等。
- CORS机制
- 定义:CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种基于HTTP头的机制,允许服务器明确告诉浏览器哪些外部域名可以访问其资源。
- 工作原理:
- 当浏览器检测到跨域请求时,会先发送一个
OPTIONS
预检请求到目标服务器,询问服务器是否允许该请求。 - 服务器在响应中返回CORS相关的头字段,如
Access-Control-Allow-Origin
、Access-Control-Allow-Methods
等。 - 浏览器根据这些头字段决定是否允许请求继续进行。
- 当浏览器检测到跨域请求时,会先发送一个
- 关键头字段:
Access-Control-Allow-Origin
:指定允许访问的前端域名,或*
表示允许所有域名。Access-Control-Allow-Methods
:指定允许的HTTP方法。Access-Control-Allow-Headers
:指定允许的请求头字段。Access-Control-Allow-Credentials
:是否允许发送Cookie。Access-Control-Max-Age
:预检请求的缓存时间,单位为秒。
(三)实践复杂场景下的跨域配置
- 使用Spring Security的跨域配置
- 如果后端项目使用了Spring Security进行安全控制,跨域配置可能会更加复杂。因为Spring Security会拦截请求并进行认证和授权,可能会与CORS配置冲突。
- 解决方案:
-
在Spring Security的配置中,允许跨域请求的路径。例如:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable();http.authorizeRequests().antMatchers("/api/**").permitAll().anyRequest().authenticated();} }
-
确保CORS配置在Spring Security之前生效。可以通过调整配置类的加载顺序或使用
@Order
注解来实现。
-
- 跨域与Cookie管理
- 如果需要在跨域请求中携带Cookie(如登录认证),需要特别注意以下几点:
- 后端的CORS配置中,
allowCredentials
必须设置为true
,并且allowedOrigins
不能为*
。 - 前端在发送请求时,需要设置
withCredentials: true
。 - 确保Cookie的
SameSite
属性设置为None
,并且Secure
属性设置为true
(如果使用HTTPS)。
- 后端的CORS配置中,
- 跨域与GraphQL
-
如果后端使用了GraphQL,跨域配置可能会有所不同。GraphQL通常使用
POST
请求发送查询,因此需要确保CORS配置允许POST
方法。 -
示例代码:
@Configuration public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/graphql").allowedOrigins("http://localhost:5173").allowCredentials(true).allowedMethods("POST").allowedHeaders("*");} }