当前位置:   article > 正文

Spring Boot + Vue 跨域配置(CORS)问题解决历程_vue 前端cors错误解决

vue 前端cors错误解决

在使用 Spring Boot 和 Vue 开发前后端分离的项目时,跨域资源共享(CORS)问题是一个常见的挑战。接下来,我将分享我是如何一步步解决这个问题的,包括中间的一些试错过程,希望能够帮助到正在经历类似问题的你。

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.
  • 1

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

然后,在 WebSecurityConfig 中应用这个配置:

http.cors().configurationSource(corsConfigurationSource());
  • 1

结果,前端依旧报错,没有任何变化。

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

然而,前端还是无法正常发起跨域请求,这让我非常困惑。

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,
    },
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这种配置可以使前端的跨域请求通过代理转发到后端。不过,这只是开发环境下的解决方案,并没有真正解决后端的 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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

但是又出现以下错误:

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.
  • 1

这个错误信息表明,在 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.
  • 1

这意味着当 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;
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

通过以上配置,可以解决 allowCredentialsallowedOrigins"*" 冲突的问题,使得您的 Spring Boot 应用可以正确处理跨域请求。

通过以上配置,前端请求终于可以成功与后端通信,CORS 问题不再出现。

4. 为什么要这样修改

在 Spring Security 6 中,安全配置的方式有所变化。与之前版本相比,Spring Security 6 更加灵活和模块化。为了使 CORS 配置生效,我们需要:

  1. 明确指定 CORS 配置源:在 securityFilterChain 方法中,通过 http.cors(cors -> cors.configurationSource(corsConfigurationSource())) 明确指定使用我们自定义的 CorsConfigurationSource
  2. 禁用默认的 CSRF 保护:对于大多数 API 项目,特别是无状态的 RESTful 服务,禁用 CSRF 是常见的做法。通过 http.csrf().disable() 来实现。
  3. 配置异常处理和会话管理:确保我们的应用是无状态的,并且正确处理认证和授权异常。

5. 结果

经过这些配置,前端可以顺利地与后端通信,避免了 CORS 错误。整个过程让我对 CORS 配置有了更深入的理解。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/984774
推荐阅读
相关标签
  

闽ICP备14008679号