SpringBoot+Vue跨域配置(CORS)問(wèn)題得解決過(guò)程
1. 問(wèn)題描述
在我們開發(fā)的過(guò)程中,Vue 前端需要與 Spring Boot 后端通信。如果后端沒(méi)有正確配置 CORS,瀏覽器會(huì)進(jìn)行跨域檢查并阻止請(qǐng)求,報(bào)錯(cuò)信息如下:
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. 解決方案概述
為了解決這個(gè)問(wèn)題,我們需要在 Spring Boot 應(yīng)用中配置 CORS。這個(gè)過(guò)程包括創(chuàng)建一個(gè) CORS 配置類,并在 Spring Security 配置類中應(yīng)用這個(gè)配置。
3. 試錯(cuò)過(guò)程
3.1 初步嘗試:簡(jiǎn)單的 CORS 配置
我首先嘗試在 Spring Boot 中添加一個(gè)簡(jiǎn)單的 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 中應(yīng)用這個(gè)配置:
http.cors().configurationSource(corsConfigurationSource());
結(jié)果,前端依舊報(bào)錯(cuò),沒(méi)有任何變化。
3.2 細(xì)化 Security 配置
我接著嘗試在 WebSecurityConfig 中進(jìn)一步細(xì)化 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;
}
}
然而,前端還是無(wú)法正常發(fā)起跨域請(qǐng)求,這讓我非常困惑。
3.3 嘗試代理配置
為了確保開發(fā)過(guò)程中跨域請(qǐng)求能正確代理到后端,我在 Vue 項(xiàng)目中添加了代理配置:
首先,確保項(xiàng)目使用 vue-cli 創(chuàng)建,并確保有 vue.config.js 文件。然后添加如下代理配置:
let proxyObj = {};
proxyObj['/'] = {
target: 'http://localhost:8789/',
changeOrigin: true,
pathRewrite: {
'^/': ''
}
}
module.exports = {
devServer: {
open: true,
host: 'localhost',
port: 8081,
proxy: proxyObj,
},
}
這種配置可以使前端的跨域請(qǐng)求通過(guò)代理轉(zhuǎn)發(fā)到后端。不過(guò),這只是開發(fā)環(huán)境下的解決方案,并沒(méi)有真正解決后端的 CORS 配置問(wèn)題。
3.4 最終解決方案:完善的 CORS 和 Security 配置
經(jīng)過(guò)幾次嘗試和查閱資料后,我最終找到了一個(gè)有效的解決方案,結(jié)合之前的經(jīng)驗(yàn),創(chuàng)建了一個(gè)完善的 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); // 預(yù)檢請(qǐng)求的緩存時(shí)間(秒)
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig); // 對(duì)所有路徑應(yīng)用這個(gè)配置
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();
}
}
但是又出現(xiàn)以下錯(cuò)誤:
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.
這個(gè)錯(cuò)誤信息表明,在 Spring Boot 的 CORS 配置中,當(dāng) allowCredentials 設(shè)置為 true 時(shí),allowedOrigins 不能包含特殊值 "*", 因?yàn)闉g覽器不允許在 Access-Control-Allow-Origin 響應(yīng)頭中設(shè)置 "*", 同時(shí)還允許憑證(如 cookies)。此時(shí)應(yīng)該使用 allowedOriginPatterns 來(lái)代替 allowedOrigins。
具體的錯(cuò)誤原因如下:
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.
這意味著當(dāng) allowCredentials 設(shè)置為 true 時(shí),不能將 allowedOrigins 設(shè)置為 "*", 因?yàn)樗荒茉陧憫?yīng)頭中設(shè)置 Access-Control-Allow-Origin 為 "*", 同時(shí)還允許憑證。為了解決這個(gè)問(wèn)題,您需要將 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;
}
通過(guò)以上配置,可以解決 allowCredentials 和 allowedOrigins 中 "*" 沖突的問(wèn)題,使得您的 Spring Boot 應(yīng)用可以正確處理跨域請(qǐng)求。
通過(guò)以上配置,前端請(qǐng)求終于可以成功與后端通信,CORS 問(wèn)題不再出現(xiàn)。
4. 為什么要這樣修改
在 Spring Security 6 中,安全配置的方式有所變化。與之前版本相比,Spring Security 6 更加靈活和模塊化。為了使 CORS 配置生效,我們需要:
- 明確指定 CORS 配置源:在
securityFilterChain方法中,通過(guò)http.cors(cors -> cors.configurationSource(corsConfigurationSource()))明確指定使用我們自定義的CorsConfigurationSource。 - 禁用默認(rèn)的 CSRF 保護(hù):對(duì)于大多數(shù) API 項(xiàng)目,特別是無(wú)狀態(tài)的 RESTful 服務(wù),禁用 CSRF 是常見(jiàn)的做法。通過(guò)
http.csrf().disable()來(lái)實(shí)現(xiàn)。 - 配置異常處理和會(huì)話管理:確保我們的應(yīng)用是無(wú)狀態(tài)的,并且正確處理認(rèn)證和授權(quán)異常。
5. 結(jié)果
經(jīng)過(guò)這些配置,前端可以順利地與后端通信,避免了 CORS 錯(cuò)誤。整個(gè)過(guò)程讓我對(duì) CORS 配置有了更深入的理解。
以上就是SpringBoot+Vue跨域配置(CORS)問(wèn)題得解決過(guò)程的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Vue跨域配置解決的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
spring?參數(shù)校驗(yàn)Validation示例詳解
Spring提供了Validation工具類來(lái)實(shí)現(xiàn)對(duì)客戶端傳來(lái)的請(qǐng)求參數(shù)的有效校驗(yàn),本文給大家介紹spring?參數(shù)校驗(yàn)Validation示例詳解,感興趣的朋友一起看看吧2024-12-12
詳解五種方式讓你在java中讀取properties文件內(nèi)容不再是難題
這篇文章主要介紹了詳解五種方式讓你在java中讀取properties文件內(nèi)容不再是難題 ,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2016-12-12
Spring?boot?easyexcel?實(shí)現(xiàn)復(fù)合數(shù)據(jù)導(dǎo)出、按模塊導(dǎo)出功能
這篇文章主要介紹了Spring?boot?easyexcel?實(shí)現(xiàn)復(fù)合數(shù)據(jù)導(dǎo)出、按模塊導(dǎo)出,實(shí)現(xiàn)思路流程是準(zhǔn)備一個(gè)導(dǎo)出基礎(chǔ)填充模板,默認(rèn)填充key,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09
Java8語(yǔ)法糖之Lambda表達(dá)式的深入講解
這篇文章主要給大家介紹了關(guān)于Java8語(yǔ)法糖之Lambda表達(dá)式的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
Springboot?jpa使用sum()函數(shù)返回結(jié)果如何被接收
這篇文章主要介紹了Springboot?jpa使用sum()函數(shù)返回結(jié)果如何接收,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
Java詳細(xì)講解不同版本的接口語(yǔ)法和抽象類與接口的區(qū)別
對(duì)于面向?qū)ο缶幊虂?lái)說(shuō),抽象是它的一大特征之一,在?Java?中可以通過(guò)兩種形式來(lái)體現(xiàn)OOP的抽象:接口和抽象類,下面這篇文章主要給大家介紹了關(guān)于Java入門基礎(chǔ)之抽象類與接口的相關(guān)資料,需要的朋友可以參考下2022-04-04

