SpringBoot+Vue跨域配置(CORS)问题得解决过程
作者:繁依Fanyi
1. 问题描述
在我们开发的过程中,Vue 前端需要与 Spring Boot 后端通信。如果后端没有正确配置 CORS,浏览器会进行跨域检查并阻止请求,报错信息如下:
Access to XMLHttpRequest at 'http://localhost:8789/auth/register' from origin 'http://localhost:8081' 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.
2. 解决方案概述
为了解决这个问题,我们需要在 Spring Boot 应用中配置 CORS。这个过程包括创建一个 CORS 配置类,并在 Spring Security 配置类中应用这个配置。
3. 试错过程
3.1 初步尝试:简单的 CORS 配置
我首先尝试在 Spring Boot 中添加一个简单的 CORS 配置类:
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; @Configuration public class CorsConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.addAllowedOrigin("*"); configuration.addAllowedMethod("*"); configuration.addAllowedHeader("*"); configuration.setAllowCredentials(true); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
然后,在 WebSecurityConfig
中应用这个配置:
http.cors().configurationSource(corsConfigurationSource());
结果,前端依旧报错,没有任何变化。
3.2 细化 Security 配置
我接着尝试在 WebSecurityConfig
中进一步细化 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.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors().configurationSource(corsConfigurationSource()) .and().csrf().disable(); } @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.addAllowedOrigin("*"); configuration.addAllowedMethod("*"); configuration.addAllowedHeader("*"); configuration.setAllowCredentials(true); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
然而,前端还是无法正常发起跨域请求,这让我非常困惑。
3.3 尝试代理配置
为了确保开发过程中跨域请求能正确代理到后端,我在 Vue 项目中添加了代理配置:
首先,确保项目使用 vue-cli
创建,并确保有 vue.config.js
文件。然后添加如下代理配置:
let proxyObj = {}; proxyObj['/'] = { target: 'http://localhost:8789/', changeOrigin: true, pathRewrite: { '^/': '' } } module.exports = { devServer: { open: true, host: 'localhost', port: 8081, proxy: proxyObj, }, }
这种配置可以使前端的跨域请求通过代理转发到后端。不过,这只是开发环境下的解决方案,并没有真正解决后端的 CORS 配置问题。
3.4 最终解决方案:完善的 CORS 和 Security 配置
经过几次尝试和查阅资料后,我最终找到了一个有效的解决方案,结合之前的经验,创建了一个完善的 CORS 和 Security 配置。
CorsConfig.java
package cn.techfanyi.fanyi.config; 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; @Configuration public class CorsConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration corsConfig = new CorsConfiguration(); corsConfig.addAllowedOriginPattern("*"); // 允许任何源 corsConfig.addAllowedMethod("*"); // 允许任何HTTP方法 corsConfig.addAllowedHeader("*"); // 允许任何HTTP头 corsConfig.setAllowCredentials(true); // 允许证书(cookies) corsConfig.setMaxAge(3600L); // 预检请求的缓存时间(秒) UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfig); // 对所有路径应用这个配置 return source; } }
WebSecurityConfig.java
package cn.techfanyi.fanyi.config; import cn.techfanyi.fanyi.filter.JwtRequestFilter; import cn.techfanyi.fanyi.security.CustomAccessDeniedHandler; import cn.techfanyi.fanyi.security.CustomAuthenticationEntryPoint; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig { private final JwtRequestFilter jwtRequestFilter; private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; private final CustomAccessDeniedHandler customAccessDeniedHandler; public WebSecurityConfig(JwtRequestFilter jwtRequestFilter, CustomAuthenticationEntryPoint customAuthenticationEntryPoint, CustomAccessDeniedHandler customAccessDeniedHandler) { this.jwtRequestFilter = jwtRequestFilter; this.customAuthenticationEntryPoint = customAuthenticationEntryPoint; this.customAccessDeniedHandler = customAccessDeniedHandler; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable() .cors(cors -> cors.configurationSource(corsConfigurationSource())) .authorizeRequests(authorizedRequests -> authorizedRequests.requestMatchers("/**").permitAll() .anyRequest().authenticated()) .exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(customAuthenticationEntryPoint) .accessDeniedHandler(customAccessDeniedHandler)) .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } private CorsConfigurationSource corsConfigurationSource() { return new CorsConfig().corsConfigurationSource(); } }
但是又出现以下错误:
java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
这个错误信息表明,在 Spring Boot 的 CORS 配置中,当 allowCredentials 设置为 true 时,allowedOrigins 不能包含特殊值 "*", 因为浏览器不允许在 Access-Control-Allow-Origin 响应头中设置 "*", 同时还允许凭证(如 cookies)。此时应该使用 allowedOriginPatterns 来代替 allowedOrigins。
具体的错误原因如下:
java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
这意味着当 allowCredentials 设置为 true 时,不能将 allowedOrigins 设置为 "*", 因为它不能在响应头中设置 Access-Control-Allow-Origin 为 "*", 同时还允许凭证。为了解决这个问题,您需要将 allowedOrigins 改为使用 allowedOriginPatterns。
修改 CorsConfigurationSource 如下:
@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration corsConfig = new CorsConfiguration(); corsConfig.addAllowedOriginPattern("*"); // 使用 allowedOriginPatterns 代替 allowedOrigins corsConfig.addAllowedMethod("*"); corsConfig.addAllowedHeader("*"); corsConfig.setAllowCredentials(true); corsConfig.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfig); return source; }
通过以上配置,可以解决 allowCredentials
和 allowedOrigins
中 "*"
冲突的问题,使得您的 Spring Boot 应用可以正确处理跨域请求。
通过以上配置,前端请求终于可以成功与后端通信,CORS 问题不再出现。
4. 为什么要这样修改
在 Spring Security 6 中,安全配置的方式有所变化。与之前版本相比,Spring Security 6 更加灵活和模块化。为了使 CORS 配置生效,我们需要:
- 明确指定 CORS 配置源:在
securityFilterChain
方法中,通过http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
明确指定使用我们自定义的CorsConfigurationSource
。 - 禁用默认的 CSRF 保护:对于大多数 API 项目,特别是无状态的 RESTful 服务,禁用 CSRF 是常见的做法。通过
http.csrf().disable()
来实现。 - 配置异常处理和会话管理:确保我们的应用是无状态的,并且正确处理认证和授权异常。
5. 结果
经过这些配置,前端可以顺利地与后端通信,避免了 CORS 错误。整个过程让我对 CORS 配置有了更深入的理解。
以上就是SpringBoot+Vue跨域配置(CORS)问题得解决过程的详细内容,更多关于SpringBoot Vue跨域配置解决的资料请关注脚本之家其它相关文章!