问题
Springboot升级至2.4.0中出现的跨域问题。
在Springboot 2.4.0版本之前使用的是2.3.5.RELEASE,对应的Spring版本为5.2.10.RELEASE。
升级至2.4.0后,对应的Spring版本为5.3.1。
Springboot2.3.5.RELEASE时,我们可以使用CorsFilter
设置跨域。
分析
版本2.3.5.RELEASE 设置跨域
设置代码如下:
@ConfigurationpublicclassResourcesConfigimplementsWebMvcConfigurer{@Beanpublic CorsFiltercorsFilter(){
UrlBasedCorsConfigurationSource source=newUrlBasedCorsConfigurationSource();
CorsConfiguration config=newCorsConfiguration();
config.setAllowCredentials(true);// 允许访问的客户端域名
config.addAllowedOrigin("*");// 允许服务端访问的客户端请求头
config.addAllowedHeader("*");// 允许访问的方法名,GET POST等
config.addAllowedMethod("*");// 对接口配置跨域设置
source.registerCorsConfiguration("/**", config);returnnewCorsFilter(source);}}
是允许使用*
设置允许的Origin。
这里我们看一下类CorsFilter
的源码,5.3.x版本开始,针对CorsConfiguration
新增了校验
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 href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
* @see UrlBasedCorsConfigurationSource
*/publicclassCorsFilterextendsOncePerRequestFilter{privatefinal CorsConfigurationSource configSource;private CorsProcessor processor=newDefaultCorsProcessor();/**
* Constructor accepting a {@link CorsConfigurationSource} used by the filter
* to find the {@link CorsConfiguration} to use for each incoming request.
* @see UrlBasedCorsConfigurationSource
*/publicCorsFilter(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.
*/publicvoidsetCorsProcessor(CorsProcessor processor){
Assert.notNull(processor,"CorsProcessor must not be null");this.processor= processor;}@OverrideprotectedvoiddoFilterInternal(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
方法会被执行。类中还创建了一个默认的处理类DefaultCorsProcessor
,doFilterInternal
调用this.processor.processRequest
往下
processRequest
@Override@SuppressWarnings("resource")publicbooleanprocessRequest(@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)){returntrue;}if(response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)!= null){
logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");returntrue;}boolean preFlightRequest= CorsUtils.isPreFlightRequest(request);if(config== null){if(preFlightRequest){rejectRequest(newServletServerHttpResponse(response));returnfalse;}else{returntrue;}}returnhandleInternal(newServletServerHttpRequest(request),newServletServerHttpResponse(response), config, preFlightRequest);}
进入最后一行
handleInternal
/**
* Handle the given request.
*/protectedbooleanhandleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config,boolean preFlightRequest)throws IOException{
String requestOrigin= request.getHeaders().getOrigin();
String allowOrigin=checkOrigin(config, requestOrigin);(省略...)
response.flush();returntrue;}
查看方法checkOrigin
checkOrigin
@Nullableprotected StringcheckOrigin(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
*/@Nullablepublic StringcheckOrigin(@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
*/publicvoidvalidateAllowCredentials(){if(this.allowCredentials== Boolean.TRUE&&this.allowedOrigins!= null&&this.allowedOrigins.contains(ALL)){thrownewIllegalArgumentException("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. */publicstaticfinal String ALL="*";
所以如果使用2.4.0版本,还是设置*
的话,访问API接口就会报错:
异常
java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value"*"since that cannot beset on the"Access-Control-Allow-Origin" response header. To allow credentials to aset 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
:
@ConfigurationpublicclassResourcesConfigimplementsWebMvcConfigurer{/**
* 跨域配置
*/@OverridepublicvoidaddCorsMappings(CorsRegistry registry){//对那些请求路径进行跨域处理
registry.addMapping("/**")// 允许的请求头,默认允许所有的请求头.allowedHeaders("*")// 允许的方法,默认允许GET、POST、HEAD.allowedMethods("*")// 探测请求有效时间,单位秒.maxAge(1800)// 支持的域.allowedOrigins("*");}}
方式2
继续使用CorsFilter
,使用官方推荐的allowedOriginPatterns
:
application.yml中新增配置:
(注:这里只是举个栗子…Origin的处理)
# 项目相关配置project:uiPort:8082basePath: http://localhost:${project.uiPort}
@ConfigurationpublicclassResourcesConfigimplementsWebMvcConfigurer{@Value("${project.basePath}")private String basePath;@Beanpublic CorsFiltercorsFilter(){
UrlBasedCorsConfigurationSource source=newUrlBasedCorsConfigurationSource();
CorsConfiguration config=newCorsConfiguration();
config.setAllowCredentials(true);// 允许访问的客户端域名
List<String> allowedOriginPatterns=newArrayList<>();
allowedOriginPatterns.add(basePath);
config.setAllowedOriginPatterns(allowedOriginPatterns);// config.addAllowedOrigin(serverPort);// 允许服务端访问的客户端请求头
config.addAllowedHeader("*");// 允许访问的方法名,GET POST等
config.addAllowedMethod("*");// 对接口配置跨域设置
source.registerCorsConfiguration("/**", config);returnnewCorsFilter(source);}}
完毕、