CORS跨域资源共享(三):@CrossOrigin/CorsFilter处理跨域请求示例及原理分析【享学Spring MVC】(中)

简介: CORS跨域资源共享(三):@CrossOrigin/CorsFilter处理跨域请求示例及原理分析【享学Spring MVC】(中)

Spring MVC处理CORS请求的流程


Spring MVC处理任何一个reuqest请求都会去找到它的一个处理器Handler,因此首当其冲就来到DispatcherServlet#getHandler()这个方法~


getHandler()

对于Spring MVC来说,每处理一个request请求都应该对应着一个Handler:就是DispatcherServlet.getHandler()方法来找到其对应的处理器:


DispatcherServlet:
  // 根据HttpRequest从handlerMappings找到对应的handler
  @Nullable
  protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
      // 开启Spring MVC后默认情况下handlerMappings的长度是4
      for (HandlerMapping mapping : this.handlerMappings) {
        HandlerExecutionChain handler = mapping.getHandler(request);
        if (handler != null) {
          return handler;
        }
      }
    }
    return null;
  }


handlerMappings它的长度默认是3,内容如下:


image.png


处理本例请求的是RequestMappingHandlerMapping,获取处理器的方法在父类上:


AbstractHandlerMapping:
  // 默认使用的是UrlBasedCorsConfigurationSource来管理跨域配置
  private CorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
  // 使用的都是本类的pathMatcher和urlPathHelper
  public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
    Assert.notNull(corsConfigurations, "corsConfigurations must not be null");
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.setCorsConfigurations(corsConfigurations);
    source.setPathMatcher(this.pathMatcher);
    source.setUrlPathHelper(this.urlPathHelper);
    this.corsConfigurationSource = source;
  }
  // @since 5.1 此方法出现较晚,但一般也不建议去设置
  public void setCorsConfigurationSource(CorsConfigurationSource corsConfigurationSource) {
    Assert.notNull(corsConfigurationSource, "corsConfigurationSource must not be null");
    this.corsConfigurationSource = corsConfigurationSource;
  }
  @Override
  @Nullable
  public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // getHandlerInternal这个方法是根据URL去匹配一个Handler,当然有可能是匹配不上的,那么handler就为null
    Object handler = getHandlerInternal(request);
    if (handler == null) {
      handler = getDefaultHandler();
    }
    // 若最终还是为null,那就返回null 后续的也就不再处理了
    // 它的结果是:交给下一个HandlerMapping处理,若所有的处理完后还是返回null。
    // 那就noHandlerFound(processedRequest, response) --> 404
    if (handler == null) { 
      return null;
    }
    ...
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    ...
    // 若是跨域请求,这里就继续处理,也是本文讲述具有差异性的地方所在
    if (CorsUtils.isCorsRequest(request)) {
      // 1、全局配置:从UrlBasedCorsConfigurationSource找到一个属于这个请求的配置
      // 请注意:若三种方式都没有配置,这里返回的就是null~~~
      CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
      // 2、从handler自己里找:若handler自己实现了CorsConfigurationSource接口,那就从自己这哪呗
      // 说明:此种方式适用于一个类就是一个处理器的case。比如servlet处理器
      // 所以对于@RequestMapping情况,这个值大部分情况都是null
      CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
      // 3、把全局配置和handler配置combine组合合并
      CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
      // 4、这个方法很重要。请看下面这个方法
      executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
  }
  // @since 4.2
  protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
    // 若是预检请求:就new一个新的HandlerExecutionChain。
    // PreFlightHandler是一个HttpRequestHandler哦~~~并且实现了接口CorsConfigurationSource
    if (CorsUtils.isPreFlightRequest(request)) {
      HandlerInterceptor[] interceptors = chain.getInterceptors();
      chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
    }
    // 若不是预检请求,就添加一个拦截器CorsInterceptor
    // 注意:这个拦截器只会作用于这个chain哦(也就是这个handler~~~) 
    // 能进来这里是简单请求 或者 真实请求。
    else {
      chain.addInterceptor(new CorsInterceptor(config));
    }
    return chain;
  }


根据URL成功匹配到一个Handler后,若是跨域请求就会继续添加跨域部分的处理逻辑:


  • 若是预检请求:针对此请求会直接new一个PreFlightHandler作为HttpRequestHandler处理器来处理它,而不再是交给匹配上的Handler去处理(这点特别的重要)

   - PreFlightHandler#handle方法委托给了corsProcessor去处理跨域请求头、响应头的

    - 值得注意的是:此时即使原Handler它不执行了,但匹配上的HandlerInterceptor们仍都还是会生效执行作用在OPTIONS方法上的

  • 若是简单请求/真实请求:在原来的处理链上加一个拦截器chain.addInterceptor(new CorsInterceptor(config)),由这个拦截器它最终复杂来处理相关逻辑(全权委托给corsProcessor)


核心的处理步骤就这么简单,理解起来也并不困难。因此我们还非常有必要的就是这三种配置方式是如何被初始化的呢?


CorsFilter方式初始化


要让它生效就需要我们手动把它注册进Servlet容器内,由它“拦截请求”自己来完成CorsProcessor.processRequest(corsConfiguration, request, response)这些处理操作。所以它和后续的getHandler()等这些处理逻辑是关系不大的。

此种方式的优雅程度上和自己实现差异并不大,因此我个人是不太推荐的~~


WebMvcConfigurer.addCorsMappings()方式初始化


这种方式是我推荐的,它的基本原理和我之前说过的WebMvcConfigurer其它配置项差不多。它作用的地方就是下面我列出的4个HandlerMapping初始化的时候。


WebMvcConfigurationSupport:
  @Bean
  public RequestMappingHandlerMapping requestMappingHandlerMapping() {
    ...
  }
  // 最终返回的是个SimpleUrlHandlerMapping 可以直接完成映射
  @Bean
  @Nullable
  public HandlerMapping viewControllerHandlerMapping() {
    ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
    ... 
  }
  // 按照bean名称进行匹配处理器
  @Bean
  public BeanNameUrlHandlerMapping beanNameHandlerMapping() {}
  // 最终也是个SimpleUrlHandlerMapping 
  @Bean
  @Nullable
  public HandlerMapping resourceHandlerMapping() {}


他们四个初始化时最终都调用了同一个方法:mapping.setCorsConfigurations(getCorsConfigurations())设置CORS配置,此方法是父类AbstractHandlerMapping提供的,原理可参考CorsRegistry和CorsRegistration .



相关文章
|
XML Java 数据格式
肝了30天总结,史上最全面透彻的Spring核心原理分析和27道高频面试题
在阅读面试题之前,小伙伴们可以先看看我之前发布的系列文章,Spring核心原理包括源码分析和用30个类手写。面试刷题固然很重要,但是知其然知其所以然更重要。
2333 4
肝了30天总结,史上最全面透彻的Spring核心原理分析和27道高频面试题
|
druid Java 数据库连接
SpringBoot原理分析 | Spring Data整合:JDBC、Druid、Mybatis
SpringBoot原理分析 | Spring Data整合:JDBC、Druid、Mybatis
538 0
|
存储 XML 前端开发
“自定义MVC原理解析与示例:打造优雅、可定制化的Java应用程序”(上)
“自定义MVC原理解析与示例:打造优雅、可定制化的Java应用程序”
235 0
|
前端开发 安全 JavaScript
Spring Boot2 系列教程(十四)CORS 解决跨域问题
Spring Boot2 系列教程(十四)CORS 解决跨域问题
|
JavaScript 前端开发 API
跨域资源共享(CORS)的工作原理是什么?
跨域资源共享(CORS)通过浏览器和服务器之间的这种交互机制,在保证安全性的前提下,实现了跨域资源的访问,使得不同源的网页能够合法地获取和共享服务器端的资源,为现代Web应用的开发提供了更大的灵活性和扩展性。
|
Java Spring
快速解决Spring Boot跨域困扰:使用CORS实现无缝跨域支持
这是一个简单的配置示例,用于在Spring Boot应用程序中实现CORS支持。根据你的项目需求,你可能需要更详细的配置来限制允许的来源、方法和标头。
1618 3
|
前端开发 JavaScript Java
使用Spring Boot实现跨域资源共享(CORS)
使用Spring Boot实现跨域资源共享(CORS)
|
前端开发 JavaScript Java
使用Spring Boot实现跨域资源共享(CORS)
使用Spring Boot实现跨域资源共享(CORS)
|
前端开发 安全 Java
Spring Boot中的CORS配置
Spring Boot中的CORS配置
|
JavaScript 前端开发 安全
JavaScript中跨域资源共享(CORS):原理和解决方案
【4月更文挑战第22天】本文介绍了JavaScript中跨域资源共享(CORS)的原理和解决方案。CORS借助HTTP头部字段允许跨域请求,核心是Access-Control-Allow-Origin响应头。解决方案包括:服务器端设置响应头(如使用Express.js的cors中间件)、使用代理服务器或JSONP。现代Web开发推荐使用CORS,因为它更安全、灵活,而JSONP已逐渐被淘汰。理解并正确实施CORS能提升Web应用性能和安全性。
1090 0