Springboot升级至2.4.0中出现的跨域问题分析及修改方案
lcjasas 人气:0问题
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 设置跨域
设置代码如下:
@Configuration public class ResourcesConfig implements WebMvcConfigurer { @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 允许访问的客户端域名 config.addAllowedOrigin("*"); // 允许服务端访问的客户端请求头 config.addAllowedHeader("*"); // 允许访问的方法名,GET POST等 config.addAllowedMethod("*"); // 对接口配置跨域设置 source.registerCorsConfiguration("/**" , config); return new CorsFilter(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/" 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
方法会被执行。类中还创建了一个默认的处理类DefaultCorsProcessor
,doFilterInternal
调用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); }
进入最后一行
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版本,还是设置*
的话,访问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) { //对那些请求路径进行跨域处理 registry.addMapping("/**") // 允许的请求头,默认允许所有的请求头 .allowedHeaders("*") // 允许的方法,默认允许GET、POST、HEAD .allowedMethods("*") // 探测请求有效时间,单位秒 .maxAge(1800) // 支持的域 .allowedOrigins("*"); } }
方式2
继续使用CorsFilter
,使用官方推荐的allowedOriginPatterns
:
application.yml中新增配置:
(注:这里只是举个栗子…Origin的处理)
# 项目相关配置 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); // 允许服务端访问的客户端请求头 config.addAllowedHeader("*"); // 允许访问的方法名,GET POST等 config.addAllowedMethod("*"); // 对接口配置跨域设置 source.registerCorsConfiguration("/**" , config); return new CorsFilter(source); } }
加载全部内容