全局跨域配置問題

先說問題,如果你已經知道怎麼處理,請忽略。
項目中有個全局跨域配置,正常請求前端不會有跨域問題,如果在攔截器中拋出錯誤,前端就會有問題。

原因分析

既然在攔截器中報錯,就有跨域問題,那就說明這個跨域配置並沒有起作用。
當時的跨域配置是如下這樣

@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);
    }
}

那麼這個配置爲什麼沒起作用呢?我們先來看一張圖。


這是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前後起作用,和執行順序
下面是兩者的區別


執行順序
過濾器前->攔截器前->Action處理->攔截器後->過濾器後



本文分享自微信公衆號 - 阿提說說(itsaysay)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章