Springboot升級至2.4.0中出現(xiàn)的跨域問題分析及修改方案
問題
Springboot升級至2.4.0中出現(xiàn)的跨域問題。
在Springboot 2.4.0版本之前使用的是2.3.5.RELEASE,對應(yīng)的Spring版本為5.2.10.RELEASE。
升級至2.4.0后,對應(yīng)的Spring版本為5.3.1。
Springboot2.3.5.RELEASE時,我們可以使用CorsFilter設(shè)置跨域。
分析
版本2.3.5.RELEASE 設(shè)置跨域
設(shè)置代碼如下:
@Configuration
public class ResourcesConfig implements WebMvcConfigurer {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 允許訪問的客戶端域名
config.addAllowedOrigin("*");
// 允許服務(wù)端訪問的客戶端請求頭
config.addAllowedHeader("*");
// 允許訪問的方法名,GET POST等
config.addAllowedMethod("*");
// 對接口配置跨域設(shè)置
source.registerCorsConfiguration("/**" , config);
return new CorsFilter(source);
}
}
是允許使用*設(shè)置允許的Origin。
這里我們看一下類CorsFilter的源碼,5.3.x版本開始,針對CorsConfiguration新增了校驗(yàn)
5.3.x源碼分析 CorsFilter
/**
* {@link javax.servlet.Filter} to handle CORS pre-flight requests and intercept
* CORS simple and actual requests with a {@link CorsProcessor}, and to update
* the response, e.g. with CORS response headers, based on the policy matched
* through the provided {@link CorsConfigurationSource}.
*
* <p>This is an alternative to configuring CORS in the Spring MVC Java config
* and the Spring MVC XML namespace. It is useful for applications depending
* only on spring-web (not on spring-webmvc) or for security constraints that
* require CORS checks to be performed at {@link javax.servlet.Filter} level.
*
* <p>This filter could be used in conjunction with {@link DelegatingFilterProxy}
* in order to help with its initialization.
*
* @author Sebastien Deleuze
* @since 4.2
* @see <a rel="external nofollow" >CORS W3C recommendation</a>
* @see UrlBasedCorsConfigurationSource
*/
public class CorsFilter extends OncePerRequestFilter {
private final CorsConfigurationSource configSource;
private CorsProcessor processor = new DefaultCorsProcessor();
/**
* Constructor accepting a {@link CorsConfigurationSource} used by the filter
* to find the {@link CorsConfiguration} to use for each incoming request.
* @see UrlBasedCorsConfigurationSource
*/
public CorsFilter(CorsConfigurationSource configSource) {
Assert.notNull(configSource, "CorsConfigurationSource must not be null");
this.configSource = configSource;
}
/**
* Configure a custom {@link CorsProcessor} to use to apply the matched
* {@link CorsConfiguration} for a request.
* <p>By default {@link DefaultCorsProcessor} is used.
*/
public void setCorsProcessor(CorsProcessor processor) {
Assert.notNull(processor, "CorsProcessor must not be null");
this.processor = processor;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
if (!isValid || CorsUtils.isPreFlightRequest(request)) {
return;
}
filterChain.doFilter(request, response);
}
}
類CorsFilter繼承自OncePerRequestFilter,doFilterInternal方法會被執(zhí)行。類中還創(chuàng)建了一個默認(rèn)的處理類DefaultCorsProcessor,doFilterInternal調(diào)用this.processor.processRequest
往下
processRequest
@Override
@SuppressWarnings("resource")
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
HttpServletResponse response) throws IOException {
Collection<String> varyHeaders = response.getHeaders(HttpHeaders.VARY);
if (!varyHeaders.contains(HttpHeaders.ORIGIN)) {
response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
}
if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD)) {
response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
}
if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS)) {
response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
}
if (!CorsUtils.isCorsRequest(request)) {
return true;
}
if (response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) != null) {
logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");
return true;
}
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
if (config == null) {
if (preFlightRequest) {
rejectRequest(new ServletServerHttpResponse(response));
return false;
}
else {
return true;
}
}
return handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest);
}
進(jìn)入最后一行
handleInternal
/**
* Handle the given request.
*/
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config, boolean preFlightRequest) throws IOException {
String requestOrigin = request.getHeaders().getOrigin();
String allowOrigin = checkOrigin(config, requestOrigin);
(省略...)
response.flush();
return true;
}
查看方法checkOrigin
checkOrigin
@Nullable
protected String checkOrigin(CorsConfiguration config, @Nullable String requestOrigin) {
return config.checkOrigin(requestOrigin);
}
Go on
checkOrigin
/**
* Check the origin of the request against the configured allowed origins.
* @param requestOrigin the origin to check
* @return the origin to use for the response, or {@code null} which
* means the request origin is not allowed
*/
@Nullable
public String checkOrigin(@Nullable String requestOrigin) {
if (!StringUtils.hasText(requestOrigin)) {
return null;
}
if (!ObjectUtils.isEmpty(this.allowedOrigins)) {
if (this.allowedOrigins.contains(ALL)) {
validateAllowCredentials();
return ALL;
}
for (String allowedOrigin : this.allowedOrigins) {
if (requestOrigin.equalsIgnoreCase(allowedOrigin)) {
return requestOrigin;
}
}
}
if (!ObjectUtils.isEmpty(this.allowedOriginPatterns)) {
for (OriginPattern p : this.allowedOriginPatterns) {
if (p.getDeclaredPattern().equals(ALL) || p.getPattern().matcher(requestOrigin).matches()) {
return requestOrigin;
}
}
}
return null;
}
方法validateAllowCredentials
validateAllowCredentials
/**
* Validate that when {@link #setAllowCredentials allowCredentials} is true,
* {@link #setAllowedOrigins allowedOrigins} does not contain the special
* value {@code "*"} since in that case the "Access-Control-Allow-Origin"
* cannot be set to {@code "*"}.
* @throws IllegalArgumentException if the validation fails
* @since 5.3
*/
public void validateAllowCredentials() {
if (this.allowCredentials == Boolean.TRUE &&
this.allowedOrigins != null && this.allowedOrigins.contains(ALL)) {
throw new 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.");
}
看一下ALL是什么
/** Wildcard representing <em>all</em> origins, methods, or headers. */ public static final String ALL = "*";
所以如果使用2.4.0版本,還是設(shè)置*的話,訪問API接口就會報錯:
異常
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. at org.springframework.web.cors.CorsConfiguration.validateAllowCredentials(CorsConfiguration.java:457) at org.springframework.web.cors.CorsConfiguration.checkOrigin(CorsConfiguration.java:561) at org.springframework.web.cors.DefaultCorsProcessor.checkOrigin(DefaultCorsProcessor.java:174) at org.springframework.web.cors.DefaultCorsProcessor.handleInternal(DefaultCorsProcessor.java:116) at org.springframework.web.cors.DefaultCorsProcessor.processRequest(DefaultCorsProcessor.java:95) at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:87) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748)
修改方式
方式1
不使用CorsFilter,直接重寫方法addCorsMappings:
@Configuration
public class ResourcesConfig implements WebMvcConfigurer {
/**
* 跨域配置
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
//對那些請求路徑進(jìn)行跨域處理
registry.addMapping("/**")
// 允許的請求頭,默認(rèn)允許所有的請求頭
.allowedHeaders("*")
// 允許的方法,默認(rèn)允許GET、POST、HEAD
.allowedMethods("*")
// 探測請求有效時間,單位秒
.maxAge(1800)
// 支持的域
.allowedOrigins("*");
}
}
方式2
繼續(xù)使用CorsFilter,使用官方推薦的allowedOriginPatterns:
application.yml中新增配置:
(注:這里只是舉個栗子…Origin的處理)
# 項(xiàng)目相關(guān)配置
project:
uiPort: 8082
basePath: http://localhost:${project.uiPort}
@Configuration
public class ResourcesConfig implements WebMvcConfigurer {
@Value("${project.basePath}")
private String basePath;
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 允許訪問的客戶端域名
List<String> allowedOriginPatterns = new ArrayList<>();
allowedOriginPatterns.add(basePath);
config.setAllowedOriginPatterns(allowedOriginPatterns);
// config.addAllowedOrigin(serverPort);
// 允許服務(wù)端訪問的客戶端請求頭
config.addAllowedHeader("*");
// 允許訪問的方法名,GET POST等
config.addAllowedMethod("*");
// 對接口配置跨域設(shè)置
source.registerCorsConfiguration("/**" , config);
return new CorsFilter(source);
}
}
到此這篇關(guān)于Springboot升級至2.4.0中出現(xiàn)的跨域問題分析及修改方案的文章就介紹到這了,更多相關(guān)Springboot2.4.0跨域內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解JAVA高質(zhì)量代碼之?dāng)?shù)組與集合
在學(xué)習(xí)編程的過程中,我覺得不止要獲得課本的知識,更多的是通過學(xué)習(xí)技術(shù)知識提高解決問題的能力,這樣我們才能走在最前方,本文主要講述Java高質(zhì)量代碼之?dāng)?shù)組與集合2013-08-08
@CacheEvict中的allEntries與beforeInvocation的區(qū)別說明
這篇文章主要介紹了@CacheEvict中的allEntries與beforeInvocation的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12

