1. 背景分析
众所周知CorsFilter是Spring提供的跨域过滤器,我们可能会做以下的配置,基本上就是允许任何跨域请求
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
//允许所有域名进行跨域调用
config.addAllowedOriginPattern("*");
//允许跨越发送cookie
config.setAllowCredentials(true);
//放行全部原始头信息
config.addAllowedHeader("*");
//允许所有请求方法跨域调用
config.addAllowedMethod("*");
//跨域允许时间,单位:秒
config.setMaxAge(60 * 60L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
我们的系统主要是SpringBoot+SpringSecurity构成的,我利用Spring的CorsFilter做跨域操作也没问题吧
但就是在这种简单的情况下前端还是爆出了跨域情况
这就头大了呀,毕竟网上的教程和我的经验分析都觉得是可以的
2. 问题分析
这个时候就没办法了,只能本地Debug + 分析源码,看看问题所在地
然后我是利用Apifox里面保存的请求实例,然后复制一份(这里面有系统的认证参数
),请求方式改为了OPTIAN了,然后其他杂七杂八的请求头也从前端中复制到新的请求实例中了
然后启动项目,打好断点
最终发现进入了CorsFilter的doFilterInternal方法,并且判断成功,确实是一个OPTIAON, 响应头也已经填充了对应的响应头,ApiFox也提示请求正常
这就离谱了,dev环境不可以,local环境就可以了,这个时候我决定不偷懒了,新建一个请求实例,完全照搬前端发起的请求
这个时候问题就稍微浮出水面了,我发现CorsFilter
都没进去,Apifox就已经提示了跨域了
于是我继续分析向上的流程,大家看下面的这个图,这是注册到Tomcat中的过滤器组成的过滤器链
先看前三个过滤器:
- OrderedCharacterEncodingFilter:确认本次请求的编码格式
- OrderedCharacterEncodingFilter:为PUT、PATCH、DELETE这样的请求方式,将请求体转为键值对形式,然后通过 getParameter 读取
- OrderedRequestContextFilter:用于初始化 LocaleContextHolder 和 RequestContextHolder 的过滤器
然后我们再看后面的springSecurityFilterChain
和corsFilter
,注意这里是springSecurityFilterChain
在前,也就是会先执行
我最新的这一次跨域请求是没有携带认证参数的,也就是说会被SpringSecurity的ExceptionTranslationFilter
处理访问被拒绝异常,也就不会走后面的corsFilter
,也就不会写入响应头的相关参数了
3. 解决办法
3.1 SpringSecurity的角度
问题分析到这其实解决思路就很简单了,要么我们改用SpringSecurity提供的跨域支持,就像下面这样
3.2 SpringBoot的角度
要么我们就改变注册到Tomcat的CorsFilter
的执行顺序,我当时也是这样处理的,看下图我介绍的三个过滤器均在
springSecurityFilterChain
之前执行,并且他们的顺序是保持一致的,也就是SpringBoot一定做了排序的
我们可以先看这三个过滤器的共同点:他们都同时实现了OrderedFilter
接口,并且重写getOrder()
方法
在这种情况下,我就自己写了一个CorsFilter, 并实现了OrderedFilter
然后我们就可以发现过滤器的顺序发生了变化
前端也正常的发起了跨域请求,业务流程也可以继续跑了
4. 总结
总结:
- 其实跨域问题很好解决,本质上就是后端要设置对应的响应头
- 我这里出现问题还是因为自己偷懒了,直接复制以前的请求实例发起OPTION请求
- 还有就是没想起来SpringBoot和SpringSecurtiy中处理跨域会有一个先后顺序(冲突)的问题才导致的
- 当然Spring中处理跨域其实不只CorsFilter这一种方式,还可以通过
RequestMappingHandlerMapping
完成,就在下面的地方,这个后续在介绍