项目场景:
springboot+shiro+redis+jwt前后端分离,整合思路正在逐步完善,本篇只讨论整合过程中的前后端分离跨域的一些问题和坑
问题描述:
- springboot中几种跨域方式的区别,以及个人认为最正常的处理
- 坑我一晚上的问题:过滤器之间的优先级,尤其对于跨域过滤器尤为重要
- 前后端分离后,跨域cookie传送的解决办法
问题分析:
1. springboot的几种跨域方式:
- 实现WebMvcConfigurer或子类的addCorsMappings方式
可以理解为类似于拦截器,但又不是拦截器,因为他与拦截器同时存在时,跨域会冲突甚至失效
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").
allowedOriginPatterns("*"). //允许跨域的域名,可以用*表示允许任何域名使用
// allowedOrigins("*"). //在Springboot2.4对应Spring5.3后在设置allowCredentials(true)的基础上不能直接使用通配符设置allowedOrigins,而是需要指定特定的URL。如果需要设置通配符,需要通过allowedOriginPatterns指定
allowedMethods("GET", "POST", "DELETE", "PUT") . //允许任何方法(post、get等)
allowedHeaders("*"). //允许任何请求头
allowCredentials(true). //带上cookie信息
exposedHeaders(HttpHeaders.SET_COOKIE).maxAge(3600L); //maxAge(3600)表明在3600秒内,不需要再发送预检验请求,可以缓存该结果
}
}
- 使用spring内置的CorsFilter,凡是过滤器都会有坑,下面解答
spring为我们提供了他内置的corsFilter,我们只需要填入相应参数即可使用,他的本质是Filter过滤器,不是拦截器,属于servlet范畴。
/**
* 配置跨域过滤器
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedOrigin("http://localhost:9999");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedHeader("*");
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(corsConfigurationSource);
}
- 使用传统自定义Filter,同样过滤器都有坑
这个就不用细说了,跨域处理的开山鼻祖,谁都知道,记得实现Filter接口,在此只贴出部分代码
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority, content-type, version-info, X-Requested-With");
filterChain.doFilter(servletRequest, servletResponse);
}
2.过滤器优先级之间的关系:
简单来说,就是如果你定义了其他过滤器去进行请求拦截的逻辑,同时也定义了跨域过滤器,如果跨域优先级不够,那么,请求就不会进入跨域过滤器,虽然被拦截,但它不会报错,而去显示跨域错误。这么说有点笼统,我用自己的实例来说明。
本项目使用了shiro+jwt,自然而然会去重写shiro的Filter去拦截没有登录和授权的用户,同时我这里还需要跨域。那么,仔细想想,过滤器也有顺序链。如果一个请求被shiro的过滤器拦截,他没有进入到跨域过滤器中就已经被拦截,那么此时,无论你返回什么,前端都只会显示跨域失败的错误。也就是这样
你甚至可以显而易见的看到,我返回的状态码为200,200代表请求成功。我的本意是如果没有登录则返回未登录的json信息·······
原因就是,这个请求没有进入跨域过滤器中。
解决方案:
大家都知道,filter有一个order属性,他是设置优先级的,order值越小优先级越高。说到这儿大家应该已经明白,让跨域过滤器优先级最高就好,这里我就不说传统方法了,大家举一反三
/**
* 配置跨域过滤器,跨域过滤器一定要限制性
*/
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedOrigin("http://localhost:9999");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedHeader("*");
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
FilterRegistrationBean<CorsFilter> corsFilterFilterRegistrationBean = new FilterRegistrationBean<>(new CorsFilter(corsConfigurationSource));
//重点在此
corsFilterFilterRegistrationBean.setOrder(0);
return corsFilterFilterRegistrationBean;
}
3.跨域cookie的携带:
由于最近的项目都是前后端分离。jwt或token的存储已经试过localStorage、sessionStorage、Cookie。也查阅了许多资料,也看了大多网站的处理办法。基于三者的安全考虑的话(三者安全大家自行查阅,百度一抓一把),cookie是相对最安全的。但是跨域cookie的处理初学者包括我也很头痛。具体原理不知道,我现在只知道怎么做。
首先后端跨域处理是必须的,同时,跨域时必须让一个属性打开,也就是,对应上面的跨域处理的第二种方法
corsConfiguration.setAllowCredentials(true);
至于对应的请求头,大家自行百度。
前端处理也很好说,建议封装ajax等等,这样比较方便,这里只贴出jquery的处理。重点:不仅请求的接口需要带上,获取cookie的接口也必须写上,否则,浏览器会忽略。其他ajax方式,类似,基本上都是这个属性
$.ajax({
url: "http://localhost/test",
xhrFields: {
withCredentials: true
},
crossDomain: true,
success: function (data) {
console.log(data)
}
})