Spring处理跨域&跨域配置不生效问题
文章目录
前言
一旦前后端分离,通过前端去访问后台接口,就很容易遇到跨域访问失败的问题。什么是跨域这里不做赘述,百度一下就会找到很多答案。 本文主要介绍通过Spring/Spring boot配置跨域的处理,以及分析如果遇到配置不生效的问题的可能原因
一、处理跨域问题
1.实现Filter接口的方式(spring/springboot通用)
在filter中,配置上Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers等跨域的相关参数。一般如果自定义请求头参数的时候,需要在Access-Control-Allow-Headers上配置上自定义的请求头参数,否则在OPTIONS请求时,若后端没有返回这些header参数,前端会抛出参数不识别的错误。
对应响应的跨域配置
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setCharacterEncoding("UTF-8");
//配置来源 '*'
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
//允许获取证书
response.setHeader("Access-Control-Allow-Credentials", "true");
//配置请求方法
response.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS");
//配置请求头,如果有自定义的请求头,必须配置
response.setHeader("Access-Control-Allow-Headers",
"Content-type, X-Requested-With, Authorization, myKey, my-Date, Content-xxx, Accept");
response.setHeader("Content-Type", "application/json;charset=UTF-8");
response.setHeader("Access-Control-Max-Age", "3600");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
然后将filter配置启动实例化即可。 此处registrationBean.setOrder(1)并不是一定需要将order设值为1才能生效,设值为Integer.MAX_VALUE也是同样的效果,(如果这里不设值,默认就是Integer.MAX_VALUE)这个表示的只是最低优先级,也就是他可能是过滤器链中的最后一个节点,除非这个过滤器链在中途就退出了,否则它也还是会被执行到。设值为1的目的,就是防止有中途退出的过滤器节点,导致该过滤器没有被执行到
@Bean
public FilterRegistrationBean corsFilterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new CorsFilter());
registrationBean.addUrlPatterns("/*"); //所有路径请求
registrationBean.setName("CorsFilter");
registrationBean.setOrder(1);
return registrationBean;
}
2.springboot的配置
spring boot的处理跨域方式会比实现过滤器的方式会更加简便方便,但是其本质也是将跨域的配置注册到过滤器中去。 对于springboot1.x和springboot2.x,其配置还有一定的区别,此处直接引用https://www.jianshu.com/p/cbc5470838da
1、SpringBoot 1.X
给项目新建一个 CorsConfig 类
详细代码如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); // 1允许任何域名使用
corsConfiguration.addAllowedHeader("*"); // 2允许任何头
corsConfiguration.addAllowedMethod("*"); // 3允许任何方法(post、get等)
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); // 4
return new CorsFilter(source);
}
}
2、SpringBoot 2.x
给项目新建一个 CorsConfig 类
详细代码如下:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
//设置允许跨域的路径
registry.addMapping("/**")
//设置允许跨域请求的域名
.allowedOrigins("*")
//是否允许证书 不再默认开启
.allowCredentials(true)
//设置允许的方法
.allowedMethods("*")
//跨域允许时间
.maxAge(3600);
}
}
二、配置跨域后什么原因会导致配置不生效
从跨域的配置来看,其原理是将请求的response中的跨域配置实现在过滤器里面,并将过滤器注册到spring的过滤器链中。而spring的过滤器链,执行完第一个过滤器之后,会将控制权和执行权交给下一个过滤器。所以分析原因,配置没有生效的原因就可能有两个,一个是没有执行到,一个是被后面执行的过滤器重置了值。
跨域配置没有生效?
配置了跨域之后,你可能发现实际运行结果返回的并不是我们配置的跨域参数
在前端的console里面,你也可以看到类似的错误:blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response
导致配置不生效的原因可能有两个:
1、自定义的过滤器CorsFilter没有被执行(过滤器链执行到一半就退出了)
2、自定义的过滤器CorsFilter中设值的响应头被后面执行的其他过滤器覆盖了
1.spring过滤器链未执行完退出
可以通过调试,查看自定义跨域过滤器中的FilterChain filterChain
中有哪些过滤器,debug调试滤器执行的顺序,判断是否还未执行到自己的过滤器的时候就退出了。
在实际调试的时候,发现自定义的过滤器还未执行,在一个Shiro过滤器执行后退出了
如果是遇到这种情况,可以调高自身过滤器的优先级,或降低Shiro过滤器的优先级(一般会退出过滤器链的fliter,优先级不会设值很高),所以在之前的所提到的setOrder方法,这个时候就起作用了
registrationBean.setOrder(1);
2.spring过滤器链后面执行的过滤器重置了之前的设值
同样可以通过debug调试的方式,可以知道在自定义过滤器执行完了之后,后面还有几个过滤器执行。
在自定义过滤器执行之后,开始观察跨域参数值的变化,对于执行到之后的过滤器时,若跨域参数被重置或覆盖了,那意味着就找到了导致自定义过滤器进行跨域参数设值没有生效的原因。
遇到这种情况,一般可以通过降优先级的方式,在setOrder的时候,把值设的比覆盖我们数据的过滤器大。如果不知道那个过滤器的order值,可以使用默认的最大值。
3.如果问题1和问题2同时存在
笔者刚好就遇到了这个问题,将自定义过滤器的优先级提高,则会被后面的过滤器给值覆盖了。 如果将优先级降低,则第一次请求时,在前面的过滤器就退出了。
此时,笔者先分析有些过滤器是否不必要,先去掉部分依赖。 然后如果问题仍然存在,可以先调整一个合理的过滤器执行顺序,然后通过对冲突的过滤器进行重写(相同的类,会优先加载当前项目路径下的),让过滤器链继续执行,或者调整其他过滤器修改响应头