全局跨域配置引起的问题

先说问题,如果你已经知道怎么处理,请忽略。
项目中有个全局跨域配置,正常请求前端不会有跨域问题,如果在拦截器中抛出错误,前端就会有问题。

原因分析

既然在拦截器中报错,就有跨域问题,那就说明这个跨域配置并没有起作用。
当时的跨域配置是如下这样

@Configuration
public class CorsConfiguration implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .maxAge(3600);
    }
}

那么这个配置为什么没起作用呢?我们先来看一张图。

d0e4d612c4c31d36cf13a5957547e26e749.jpg


这是Spring MVC的流程图,用户所有的请求都会经过DispatcherServlet,我们从DispatcherServlet这里开始分析,找到doDispatch方法,他上面有这样的注释。

/**
* Process the actual dispatching to the handler.
*

The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
*

All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/

注意其中的All HTTP methods are handled by this method

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //...省略
                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

       //...省略  
    }

看这个方法,其他都忽略,只看getHandler(processedRequest),它会获得HandlerExecutionChain 拦截器责任链,进入该方法

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping hm : this.handlerMappings) {
                if (logger.isTraceEnabled()) {
                    logger.trace(
                            "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
                }
                HandlerExecutionChain handler = hm.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

这只是一个循环,里面还有一个getHandler(request)方法,进入该方法

    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }

        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        //判断是否是跨域请求
        if (CorsUtils.isCorsRequest(request)) {
            //获取全局的跨域配置
            CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
           //获取方法或者类中定义的跨域配置
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
           //合并配置
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
           //获取新的HandlerExecutionChain
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
    }

代码变多了,在这里看到了我们需要的CorsConfiguration,把注意集中在这部分,进入getCorsHandlerExecutionChain(request, executionChain, config)看看

    protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
            HandlerExecutionChain chain, @Nullable 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;
    }

这里首先会判断请求是否是预检请求,预检请求是OPTIONS方法,用于检查服务器是否接受前端过来的请求
不是预检请求走else,这里将跨域配置组装成了一个Interceptor并加入到拦截器责任链中,进入addInterceptor方法中

    public void addInterceptor(HandlerInterceptor interceptor) {
        initInterceptorList().add(interceptor);
    }

   //initInterceptorList()
    private List<HandlerInterceptor> initInterceptorList() {
        if (this.interceptorList == null) {
            this.interceptorList = new ArrayList<>();
            if (this.interceptors != null) {
                // An interceptor array specified through the constructor
                CollectionUtils.mergeArrayIntoCollection(this.interceptors, this.interceptorList);
            }
        }
        this.interceptors = null;
        return this.interceptorList;
    }

你会发现initInterceptorList()其实就是一个List,而我们的跨域拦截器就被放入List的最后一个位置
因此你在自定义拦截器中抛出错误,是并不会执行到跨域拦截器的,而是直接返回了。

解决方案

使用Filter过滤器来处理跨域请求,修改后的配置

@Configuration
public class CorsConfig{
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*");
        config.setAllowCredentials(true);
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");
        config.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);
        return new CorsFilter(configSource);
    }
}

使用Filter就可以是因为,Filter在Servlet前后起作用,和执行顺序
下面是两者的区别

5ba9d33af117e31dc7fdadf2bb8d529112a.jpg


执行顺序
过滤器前->拦截器前->Action处理->拦截器后->过滤器后

 

49b711889af2674b3abe0bdb3890d7f62b4.jpg

转载于:https://my.oschina.net/itsaysay/blog/3011831

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值