背景
关于springboot和前后端分离的开发在f6car进行的是如火如荼。确实在一定程度上给大家带来了方便。
而且在跨域的问题上其实也有一些更方便的做法【某些场景毕竟只有在开发环境才会跨域】
但是此处我们不看上述的场景 单纯考虑springboot关于跨域的处理
前面我们也分析过关于前导请求的逻辑跨域二三事之options请求
分析
springboot默认启用了corsInterceptor进行跨域配置
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
我们分析到cors的interceptor和普通interceptor不一样 是由request请求过来时动态加载的
在AbstractHandlerMapping中
/**
* Update the HandlerExecutionChain for CORS-related handling.
* <p>For pre-flight requests, the default implementation replaces the selected
* handler with a simple HttpRequestHandler that invokes the configured
* {@link #setCorsProcessor}.
* <p>For actual requests, the default implementation inserts a
* HandlerInterceptor that makes CORS-related checks and adds CORS headers.
* @param request the current request
* @param chain the handler chain
* @param config the applicable CORS configuration (possibly {@code null})
* @since 4.2
*/
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, CorsConfiguration config) {
if (CorsUtils.isPreFlightRequest(request)) {
HandlerInterceptor[] interceptors = chain.getInterceptors();
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
}
else {
chain.addInterceptor(new CorsInterceptor(config));
}
return chain;
}
可以分析到当前请求如果是预检请求的话 那么此时将会使用一个新的PreFlightHandler
此时preFlightHandler替换了默认的methodHandler。而我们正常的业务逻辑其实恰好是在handler中执行。
这边的interceptor其实对于系统来说就是我们注册的业务拦截器。通常执行一些权限的等拦截。
也就是当前导请求过来时我们仍然是需要经过interceptorList的preHandle的。那么为什么当一些业务需求拦截并没有进入到
cookie业务的拦截呢???
常见的业务拦截器中我们均如下处理
if (handler instanceof HandlerMethod)
而preFlightHandler
private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource {
private final CorsConfiguration config;
public PreFlightHandler(CorsConfiguration config) {
this.config = config;
}
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
corsProcessor.processRequest(this.config, request, response);
}
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.config;
}
}
因此直接返回了true 这也进一步说明在前导请求过来时事实上是走不到真正的业务方法的。spring直接替换了相应的handler。
那么当不是前导请求而是真正的跨域请求呢???
直接在interceptor中加入了对应的corsInterceptor
chain.addInterceptor(new CorsInterceptor(config));
很明显 此时corsInterceptor是拦截器最后
private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {
private final CorsConfiguration config;
public CorsInterceptor(CorsConfiguration config) {
this.config = config;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return corsProcessor.processRequest(this.config, request, response);
}
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.config;
}
}
因此如果corsInterceptor出错 那么跨域请求在返回结果时将无法增加对应的header【由于浏览器的安全限制 此时将无法获得错误详情】
参考springboot 相关issue when interceptor preHandler throw exception, the cors is broken
解决
考虑使用spring自带的corsFilter https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("http://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
从这种角度来看其实springmvc的关于cors的实现存在瑕疵 应该将corsInterceptor在默认interceptor第一个执行 这样可以有效防止当请求失败时返回错误而前端无法感知。