解决 Spring Boot 应用 CORS 跨域问题的高级配置:一场与浏览器的爱恨情仇
各位靓仔靓女们,大家好!今天咱们来聊聊一个让无数开发者头疼,却又不得不面对的问题:CORS 跨域! 想象一下,你辛辛苦苦写了一个 Spring Boot 应用,满怀期待地部署到服务器上,结果前端小哥一调用,浏览器直接给你一个大大的红色错误:"No ‘Access-Control-Allow-Origin’ header is present on the requested resource." 是不是感觉瞬间心凉了半截?
别慌!这并不是你的代码有问题,而是浏览器出于安全考虑,启动了 CORS (Cross-Origin Resource Sharing) 机制。 CORS 就像一个严格的门卫,不允许不同源的网页之间随意访问资源。 所谓“同源”,指的是协议、域名和端口都相同。 只要其中一个不同,浏览器就会认为这是跨域请求。
今天,我们就来深入探讨 Spring Boot 应用中 CORS 的高级配置,让你彻底摆脱跨域的困扰,和浏览器握手言和! 准备好了吗? 让我们开始这场与浏览器的爱恨情仇吧!
CORS 到底是个啥?浏览器为啥这么“轴”?
在深入配置之前,我们先简单了解一下 CORS 的原理,这样才能做到知其然,更知其所以然。
浏览器之所以这么“轴”,要归功于它的安全策略——同源策略 (Same-Origin Policy)。 这个策略禁止一个源的文档或脚本访问另一个源的资源。 目的是防止恶意网站通过 JavaScript 读取你的敏感数据,比如 Cookie、LocalStorage 等。
但是,互联网的世界是开放的,不同源的网站之间互相访问资源是不可避免的。 于是,CORS 应运而生。 CORS 是一种机制,它允许服务器告诉浏览器,哪些源可以访问它的资源。
简单来说,CORS 就像一个协议,服务器通过 HTTP 响应头告诉浏览器,哪些“客人”可以进门。 浏览器收到响应后,会检查响应头中的信息,决定是否允许这次跨域请求。
Spring Boot 中解决 CORS 的几种方法
Spring Boot 提供了多种方式来解决 CORS 问题,我们由浅入深,逐一讲解:
1. 使用 @CrossOrigin
注解 (最简单粗暴的方式)
这是最简单的解决 CORS 的方法,只需要在 Controller 或方法上添加 @CrossOrigin
注解即可。
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@CrossOrigin(origins = "http://localhost:3000") // 允许来自 http://localhost:3000 的跨域请求
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, CORS!";
}
}
这段代码表示,只允许来自 http://localhost:3000
的跨域请求访问 /hello
接口。
优点: 简单易用,适用于简单的跨域场景。
缺点: 不够灵活,每个 Controller 或方法都需要添加注解,维护成本较高。 如果你需要允许所有源的访问,可以使用 @CrossOrigin(origins = "*")
, 但这在生产环境中通常是不安全的,因为它会暴露你的 API 给所有人。
2. 使用 WebMvcConfigurer
配置 (更灵活的方式)
WebMvcConfigurer
提供了更灵活的 CORS 配置方式,可以在全局范围内控制 CORS 行为。
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("/**") // 允许所有路径的跨域请求
.allowedOrigins("http://localhost:3000", "http://localhost:4200") // 允许来自这两个源的跨域请求
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的 HTTP 方法
.allowedHeaders("*") // 允许所有请求头
.allowCredentials(true) // 允许发送 Cookie
.maxAge(3600); // 预检请求的有效期,单位秒
}
}
这段代码表示,允许来自 http://localhost:3000
和 http://localhost:4200
的跨域请求访问所有路径的 API,允许的 HTTP 方法包括 GET、POST、PUT、DELETE 和 OPTIONS,允许所有请求头,并且允许发送 Cookie,预检请求的有效期为 3600 秒。
优点: 可以全局配置 CORS,更加灵活和可维护。
缺点: 需要编写配置类,稍微复杂一些。
3. 使用 Filter
(最高级的配置方式)
使用 Filter
可以对 CORS 进行更细粒度的控制,可以根据不同的请求动态地设置 CORS 响应头。
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
String origin = request.getHeader("Origin"); // 获取请求的 Origin
// 如果 Origin 不为空,则设置 CORS 响应头
if (origin != null && !origin.isEmpty()) {
response.setHeader("Access-Control-Allow-Origin", origin); // 允许来自请求 Origin 的跨域请求
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT"); // 允许的 HTTP 方法
response.setHeader("Access-Control-Max-Age", "3600"); // 预检请求的有效期
response.setHeader("Access-Control-Allow-Headers", "*"); // 允许所有请求头
response.setHeader("Access-Control-Allow-Credentials", "true"); // 允许发送 Cookie
}
// 如果是 OPTIONS 请求,则直接返回 200 OK
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res); // 继续执行请求
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化 Filter
}
@Override
public void destroy() {
// 销毁 Filter
}
}
这段代码表示,对于每个请求,都先获取请求头的 Origin,然后根据 Origin 动态地设置 CORS 响应头。 如果是 OPTIONS 请求,则直接返回 200 OK,否则继续执行请求。
优点: 可以根据不同的请求动态地设置 CORS 响应头,灵活性最高。
缺点: 代码量较多,需要了解 Servlet Filter 的原理。
CORS 配置详解:深入了解每个参数的含义
在上面的代码示例中,我们使用了一些 CORS 配置参数,下面我们来详细了解一下每个参数的含义:
参数名 | 含义 | 示例 |
---|---|---|
allowedOrigins |
允许跨域访问的源,可以是一个或多个,也可以是 * (允许所有源)。 |
allowedOrigins("http://localhost:3000", "http://localhost:4200") allowedOrigins("*") |
allowedMethods |
允许的 HTTP 方法,可以是一个或多个,常用的方法包括 GET、POST、PUT、DELETE、OPTIONS。 | allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") |
allowedHeaders |
允许的请求头,可以是一个或多个,也可以是 * (允许所有请求头)。 |
allowedHeaders("Content-Type", "Authorization") allowedHeaders("*") |
exposedHeaders |
允许浏览器访问的响应头,默认情况下,浏览器只能访问一些标准的响应头,如果需要访问自定义的响应头,需要在这里配置。 | exposedHeaders("My-Custom-Header") |
allowCredentials |
是否允许发送 Cookie,如果设置为 true ,则浏览器会在跨域请求中携带 Cookie。 注意: 如果 allowCredentials 设置为 true ,则 allowedOrigins 不能设置为 * ,必须指定具体的源。 |
allowCredentials(true) |
maxAge |
预检请求的有效期,单位秒。 浏览器在发送跨域请求之前,会先发送一个 OPTIONS 请求 (预检请求),询问服务器是否允许跨域访问。 如果服务器允许,浏览器才会发送真正的请求。 maxAge 用于指定预检请求的有效期,避免每次都发送预检请求。 |
maxAge(3600) |
重点解释一下 allowCredentials
:
allowCredentials
用于控制是否允许发送 Cookie。 默认情况下,跨域请求是不允许携带 Cookie 的。 如果你的 API 需要使用 Cookie 进行身份验证,则需要将 allowCredentials
设置为 true
。
但是! 如果 allowCredentials
设置为 true
,则 allowedOrigins
不能设置为 *
,必须指定具体的源。 这是因为如果允许所有源发送 Cookie,则可能会导致安全问题。 比如,恶意网站可以通过 JavaScript 读取你的 Cookie,从而冒充你的身份进行操作。
Spring Security 中的 CORS 配置
如果你的 Spring Boot 应用使用了 Spring Security,则需要特别注意 CORS 的配置。 因为 Spring Security 默认会拦截所有请求,包括跨域请求。
有两种方式可以在 Spring Security 中配置 CORS:
1. 使用 http.cors()
配置 (推荐)
Spring Security 提供了 http.cors()
方法,可以方便地配置 CORS。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().configurationSource(corsConfigurationSource()) // 使用自定义的 CORS 配置源
.and()
.csrf().disable() // 关闭 CSRF 保护,方便测试
.authorizeHttpRequests()
.anyRequest().permitAll(); // 允许所有请求
return http.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000", "http://localhost:4200")); // 允许来自这两个源的跨域请求
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的 HTTP 方法
configuration.setAllowedHeaders(Arrays.asList("*")); // 允许所有请求头
configuration.setAllowCredentials(true); // 允许发送 Cookie
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration); // 允许所有路径的跨域请求
return source;
}
}
这段代码表示,使用自定义的 CorsConfigurationSource
来配置 CORS。 CorsConfigurationSource
定义了 CORS 的具体配置,包括允许的源、HTTP 方法、请求头、是否允许发送 Cookie 等。
2. 使用 CorsFilter
(不推荐)
你也可以直接使用 CorsFilter
来配置 CORS,但是这种方式不太优雅,不推荐使用。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable() // 关闭 CSRF 保护,方便测试
.authorizeHttpRequests()
.anyRequest().permitAll() // 允许所有请求
.and()
.addFilter(corsFilter()); // 添加 CorsFilter
return http.build();
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("http://localhost:3000", "http://localhost:4200")); // 允许来自这两个源的跨域请求
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的 HTTP 方法
config.setAllowedHeaders(Arrays.asList("*")); // 允许所有请求头
config.setAllowCredentials(true); // 允许发送 Cookie
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
总结: 在 Spring Security 中配置 CORS,推荐使用 http.cors()
方法,因为它更加简洁和优雅。
CORS 的一些坑和注意事项
Access-Control-Allow-Origin
的优先级: 如果服务器返回了多个Access-Control-Allow-Origin
响应头,浏览器会选择优先级最高的那个。 通常,*
的优先级最低,具体的源的优先级最高。- OPTIONS 请求: 浏览器在发送跨域请求之前,会先发送一个 OPTIONS 请求 (预检请求),询问服务器是否允许跨域访问。 因此,你的服务器需要能够正确处理 OPTIONS 请求。
- Cookie 的安全: 在使用 Cookie 进行身份验证时,一定要注意 Cookie 的安全。 建议使用 HTTPS 协议,并设置 Cookie 的
Secure
属性为true
,防止 Cookie 被窃取。 - 不同浏览器的差异: 不同浏览器对 CORS 的支持程度可能有所不同。 建议在多个浏览器上进行测试,确保你的 CORS 配置能够正常工作。
总结
解决 Spring Boot 应用的 CORS 跨域问题,就像和浏览器谈恋爱一样,需要了解它的脾气,掌握它的喜好,才能最终抱得美人归。 今天我们学习了三种解决 CORS 的方法:@CrossOrigin
注解、WebMvcConfigurer
配置和 Filter
。 我们还深入了解了 CORS 的各个配置参数,以及如何在 Spring Security 中配置 CORS。 希望这些知识能够帮助你彻底摆脱跨域的困扰,让你的 Spring Boot 应用更加健壮和安全。
记住,解决 CORS 问题,不能一蹴而就,需要不断地学习和实践。 只有真正理解了 CORS 的原理,才能灵活地应对各种复杂的跨域场景。 加油! 祝你早日成为 CORS 高手!