环境信息
Spring Boot:2.0.8.RELEASE
Spring Boot内置的tomcat:tomcat-embed-core 8.5.37
问题描述
在使用浏览器访问应用,给服务端发送请求的时候,前端没有收到正确的响应,浏览器控制台和网络都报错了,提示是CORS跨域了,看接口返回的Headers里确实没有允许跨域的项(Access-Control-Allow-Origin等)。
报错提示:
响应头:
错误信息:
Access to XMLHttpRequest at 'http://localhost:6001/tfb-biz-common-service-app/query/commonQuery' from origin 'http://localhost:8804' has been
blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
POST http://localhost:6001/tfb-biz-common-service-app/query/commonQuery net::
ERR_FAILED 200
解决方案
解决思路
乍一看是CORS跨域问题,可是服务端已经进行了CORS跨域设置,之前运行也都一切正常。
打开服务器日志一看,后端在处理请求的时候,报错了,不过错误信息不是CORS跨域错误,而是用户长时间未操作:
而浏览器显示的错误是CORS跨域,这就涉及到浏览器的跨域资源共享(CORS)、同源策略规则了:
在服务端没有返回Access-Control-Allow-Origin时,浏览器端无法正常的加载服务器端响应的内容,即:服务端响应了数据(比如这里的报错信息:用户长时间未操作,请重新登录),可是响应头里没有Access-Control-Allow-Origin项,浏览器不能显示出(用户长时间未操作,请重新登录)这些信息,而是提示CORS错误。
还有一个问题,这种复杂请求,是会先发送预检请求的(OPTIONS),可以通过在浏览器网络选项卡里的筛选设置选中All来查看所有的请求:
响应头等信息:
奇怪的是,预检请求有返回Access-Control-Allow-Origin信息,并没有引发CORS错误。
原因在于,服务端的CORS设置,是在CorsFilter过滤器里判断后,添加到响应头里的。而正式请求还未执行到CorsFilter,在执行EfFilter时就已经遇到错误,响应给浏览器了,因此响应头里不包含CORS信息。
解决方法
根据前面的分析,是因为CorsFilter过滤器的执行顺序不对(优先级太低)了,导致响应头不包含CORS信息,浏览器提示CORS跨域而不显示服务端真正返回的内容。
那么只要将CorsFilter过滤器的优先级调高就行了。
原先的配置:
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsConfig {
private static Log log = LogFactory.getLog(CorsConfig.class);
@Value("${eframework.cors.allowed.origins:*}")
private String origins;
@Bean
public CorsFilter CorsConfig() {
CorsConfiguration corsConfig = new CorsConfiguration();
List<String> exposedHeaders = Arrays.asList("x-auth-token", "content-type", "X-Requested-With", "XMLHttpRequest");
List<String> allowedOrigins = Arrays.asList(StringUtils.split(origins, ","));
List<String> allowedHeaders = Arrays.asList("*");
List<String> allowedMethods = Arrays.asList("*");
corsConfig.setAllowedHeaders(allowedHeaders);
corsConfig.setAllowedMethods(allowedMethods);
corsConfig.setAllowedOrigins(allowedOrigins);
corsConfig.setExposedHeaders(exposedHeaders);
corsConfig.setMaxAge(36000L);
corsConfig.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
log.debug("跨域拦截处理启动成功允许条件为:/**");
return new CorsFilter(source);
}
}
虽然类上有@Order注解,且设置了优先级最高(值最小),可是这个注解在这里的作用是这个类的加载优先级,而不是CorsFilter过滤器的优先级,CorsFilter的优先级是默认的。
这里提供一种正确的设置方式,Spring Boot为了过滤器的优先级等设置,提供了FilterRegistrationBean这个类。
@Configuration
public class CorsConfig {
private static Log log = LogFactory.getLog(CorsConfig.class);
@Value("${eframework.cors.allowed.origins:*}")
private String origins;
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildCorsConfig());
FilterRegistrationBean<CorsFilter> filterRegistrationBean = new FilterRegistrationBean<>(new CorsFilter(source));
filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filterRegistrationBean;
}
private CorsConfiguration buildCorsConfig() {
CorsConfiguration corsConfig = new CorsConfiguration();
List<String> exposedHeaders = Arrays.asList("x-auth-token", "content-type", "X-Requested-With", "XMLHttpRequest");
List<String> allowedOrigins = Arrays.asList(StringUtils.split(origins, ","));
List<String> allowedHeaders = Arrays.asList("*");
List<String> allowedMethods = Arrays.asList("*");
corsConfig.setAllowedHeaders(allowedHeaders);
corsConfig.setAllowedMethods(allowedMethods);
corsConfig.setAllowedOrigins(allowedOrigins);
corsConfig.setExposedHeaders(exposedHeaders);
corsConfig.setMaxAge(36000L);
corsConfig.setAllowCredentials(true);
return corsConfig;
}
}
在这段代码中,filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE)设置了优先级最高,没有配置name、urlPatterns属性,使用默认值,针对所有请求("/*")进行过滤。
更改前的过滤器列表:
更改后的过滤器列表:
相关内容
之前在另一篇文章请求路径不对,预检请求preflight返回404,导致真实请求返回CORS错误里,也有一个浏览器提示了CORS,真正原因却不是的案例,原因其实都是类似的。
服务端返回内容跨域CORS之后,也在chrome/edge浏览器里显示出响应信息
Spring Boot下的过滤器优先级设置,以后再写一篇文章专门来说,@Order不一定生效,是有坑的。