SpringMVC源码解读之CORS跨域原理

SpringMVC源码解读

CORS实现解读

SpringMVC中官方推荐实现CORS的源码解读
  1. 推荐的跨域实现

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

    本质是增加一个前置的拦截器,对跨域请求进行拦截后加入相应的跨域请求头。

  2. 拦截器是依赖于SprinMVC框架的,因此可以从关键的 DispatcherServlet类入手,查看doDispatcher方法。

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            HttpServletRequest processedRequest = request;
            HandlerExecutionChain mappedHandler = null;
            boolean multipartRequestParsed = false;
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
            try {
                try {
                    ModelAndView mv = null;
                    Exception dispatchException = null;
    
                    try {
                        processedRequest = this.checkMultipart(request);
                        multipartRequestParsed = processedRequest != request;
                **      mappedHandler = this.getHandler(processedRequest);
                        if (mappedHandler == null) {
                            this.noHandlerFound(processedRequest, response);
                            return;
                        }
    
                        HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                        String method = request.getMethod();
                        boolean isGet = HttpMethod.GET.matches(method);
                        if (isGet || HttpMethod.HEAD.matches(method)) {
                            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                            if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                                return;
                            }
                        }
    
                        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                            return;
                        }
    
                        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                        if (asyncManager.isConcurrentHandlingStarted()) {
                            return;
                        }
    
                        this.applyDefaultViewName(processedRequest, mv);
                        mappedHandler.applyPostHandle(processedRequest, response, mv);
                    } catch (Exception var20) {
                        dispatchException = var20;
                    } catch (Throwable var21) {
                        dispatchException = new NestedServletException("Handler dispatch failed", var21);
                    }
    
                    this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
                } catch (Exception var22) {
                    this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
                } catch (Throwable var23) {
                    this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
                }
    
            } finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    if (mappedHandler != null) {
                        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                    }
                } else if (multipartRequestParsed) {
                    this.cleanupMultipart(processedRequest);
                }
    
            }
        }
    

    关键的步骤就在*处,由getHandler方法获取相应的 HandlerExecutionChain处理器链 ,在处理器链中包含此次请求的处理器和整个拦截器链。

  3. 进入到getHandler方法中

        @Nullable
        protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
            if (this.handlerMappings != null) {
                Iterator var2 = this.handlerMappings.iterator();
    
                while(var2.hasNext()) {
                    HandlerMapping mapping = (HandlerMapping)var2.next();
                    HandlerExecutionChain handler = mapping.getHandler(request);
                    if (handler != null) {
                        return handler;
                    }
                }
            }
    
            return null;
        }
    

    不难看出,在方法里获取了所有的处理器映射进行迭代。这里只关注 RequestMappingHandlerMapping,在执行 HandlerExecutionChain handler = mapping.getHandler(request);时,进入到getHandler方法里。

    @Nullable
        public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
            Object handler = this.getHandlerInternal(request);
            if (handler == null) {
                handler = this.getDefaultHandler();
            }
    
            if (handler == null) {
                return null;
            } else {
                if (handler instanceof String) {
                    String handlerName = (String)handler;
                    handler = this.obtainApplicationContext().getBean(handlerName);
                }
    
                if (!ServletRequestPathUtils.hasCachedPath(request)) {
                    this.initLookupPath(request);
                }
    
                HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Mapped to " + handler);
                } else if (this.logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
                    this.logger.debug("Mapped to " + executionChain.getHandler());
                }
    
             *  if (this.hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
                    CorsConfiguration config = this.getCorsConfiguration(handler, request);
                    if (this.getCorsConfigurationSource() != null) {
                        CorsConfiguration globalConfig = this.getCorsConfigurationSource().getCorsConfiguration(request);
                        config = globalConfig != null ? globalConfig.combine(config) : config;
                    }
    
                    if (config != null) {
                        config.validateAllowCredentials();
                    }
    
                    executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
                }
    
                return executionChain;
            }
        }
    

    ​ 注意 HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);语句,在这行中,将会获取此次请求的拦截器链,并将该请求对应的处理器和拦截器链包装在一起。

    ​ 此时可以观察到拦截器链中没有Cors跨域拦截器,继续执行。在*处,会进行一次判断,如果此请求已经是跨域请求,或者是非简单请求的预检请求,都会执行该语句的内容。在代码块中先确定是否配置了Cors源,如果配置了则进行Cors配置加载,然后加入到拦截器链中。如果没有配置则加入的是空。

    executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
    

    在这段方法中会对cors配置进行判断:

        protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
            if (CorsUtils.isPreFlightRequest(request)) {
                HandlerInterceptor[] interceptors = chain.getInterceptors();
                return new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
            } else {
                chain.addInterceptor(0, new CorsInterceptor(config));
                return chain;
            }
        }
    

    ​ 如果请求是预检请求则重新构建一个处理器链,不再执行之前请求映射到的处理器。如果请求不是预检请求则会建立一个CorsInterceptor拦截器加入到该处理器链,并且为了不被其他拦截器拦截跨域请求会将该跨域拦截器的顺序设置为第一个。

  4. 其实这个地方在以前的版本是存在问题的。

    ​ 在某个版本之前,跨域拦截的生成顺序是在所有拦截器的后面,此时就会存在配置好跨域,但由于设置了其他拦截器,比如做了一个登录拦截,拦截掉所有没有携带token的请求,此时就会导致跨域失败。这是因为在getHandler方法中获取到了处理器和一整个拦截器链,在 mappedHandler.applyPreHandle(processedRequest, response)中执行了拦截器链的前置方法。由于登录拦截是在跨域拦截器的前面,跨域的预检无法通过登录拦截,走不到跨域拦截器中进行跨域请求头的设置,导致了后续跨域请求的失败。

    ​ 搞清楚原理之后解决的方法就明了了。既然跨域拦截器可能会走不到,那就设置成跨域的过滤器。由于过滤器是归属于tomcat容器进行管理,而拦截器是依赖与springmvc框架,在doDispacther中进行应用,此时过滤器已经执行完了,登录拦截器即使拦截了预检请求也成功设置了跨域请求头,不影响后续的跨域请求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值