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 .



相关文章
|
2月前
|
JSON 安全 前端开发
浅析CORS跨域漏洞与JSONP劫持
浅析CORS跨域漏洞与JSONP劫持
82 3
|
8天前
|
开发框架 中间件 Java
如何处理跨域资源共享(CORS)的 OPTIONS 请求?
处理 CORS 的 OPTIONS 请求的关键是正确设置响应头,以告知浏览器是否允许跨域请求以及允许的具体条件。根据所使用的服务器端技术和框架,可以选择相应的方法来实现对 OPTIONS 请求的处理,从而确保跨域资源共享的正常进行。
|
8天前
|
JavaScript 前端开发 API
跨域资源共享(CORS)的工作原理是什么?
跨域资源共享(CORS)通过浏览器和服务器之间的这种交互机制,在保证安全性的前提下,实现了跨域资源的访问,使得不同源的网页能够合法地获取和共享服务器端的资源,为现代Web应用的开发提供了更大的灵活性和扩展性。
|
22天前
|
JSON 前端开发 安全
CORS 是什么?它是如何解决跨域问题的?
【10月更文挑战第20天】CORS 是一种通过服务器端配置和浏览器端协商来解决跨域问题的机制。它为跨域资源共享提供了一种规范和有效的方法,使得前端开发人员能够更加方便地进行跨域数据交互。
|
1月前
|
缓存 前端开发 应用服务中间件
CORS跨域+Nginx配置、Apache配置
CORS跨域+Nginx配置、Apache配置
131 7
|
1月前
|
Java BI API
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
这篇文章介绍了如何在Spring Boot项目中整合iTextPDF库来导出PDF文件,包括写入大文本和HTML代码,并分析了几种常用的Java PDF导出工具。
408 0
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
|
1月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
167 2
|
2月前
|
设计模式 Java Spring
spring源码设计模式分析(五)-策略模式
spring源码设计模式分析(五)-策略模式
|
2月前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
102 5
|
2月前
|
消息中间件 设计模式 缓存
spring源码设计模式分析(四)-观察者模式
spring源码设计模式分析(四)-观察者模式