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 .



相关文章
|
1天前
|
JSON 安全 前端开发
浅析CORS跨域漏洞与JSONP劫持
浅析CORS跨域漏洞与JSONP劫持
11 3
|
22天前
|
安全
CORS 跨域资源共享的实现原理
CORS 跨域资源共享的实现原理
|
1月前
|
Web App开发 JSON 数据格式
【Azure Developer】浏览器查看本地数据文件时遇见跨域问题(CORS)
【Azure Developer】浏览器查看本地数据文件时遇见跨域问题(CORS)
【Azure Developer】浏览器查看本地数据文件时遇见跨域问题(CORS)
|
1月前
|
API
【Azure Function】Function本地调试时遇见跨域问题(blocked by CORS policy)
【Azure Function】Function本地调试时遇见跨域问题(blocked by CORS policy)
【Azure Function】Function本地调试时遇见跨域问题(blocked by CORS policy)
|
1月前
|
安全 前端开发 Java
Web端系统开发解决跨域问题——以Java SpringBoot框架配置Cors为例
在Web安全上下文中,源(Origin)是指一个URL的协议、域名和端口号的组合。这三个部分共同定义了资源的来源,浏览器会根据这些信息来判断两个资源是否属于同一源。例如,https://www.example.com:443和http://www.example.com虽然域名相同,但由于协议和端口号不同,它们被视为不同的源。同源(Same-Origin)是指两个URL的协议、域名和端口号完全相同。只有当这些条件都满足时,浏览器才认为这两个资源来自同一源,从而允许它们之间的交互操作。
Web端系统开发解决跨域问题——以Java SpringBoot框架配置Cors为例
|
2月前
|
前端开发 Java 应用服务中间件
我以为我对Spring MVC很了解,直到我遇到了...
所有人都知道Spring MVC是是开发的,却鲜有人知道Spring MVC的理论基础来自于1978 年提出MVC模式的一个老头子,他就是Trygve Mikkjel Heyerdahl Reenskaug,挪威计算机科学家,名誉教授。Trygve Reenskaug的MVC架构思想早期用于图形用户界面(GUI) 的软件设计,他对MVC是这样解释的。MVC 被认为是解决用户控制大型复杂数据集问题的通用解决方案。最困难的部分是为不同的架构组件想出好的名字。模型-视图-编辑器是第一个。
111 1
我以为我对Spring MVC很了解,直到我遇到了...
|
1月前
|
安全 开发者 UED
|
1月前
|
前端开发 JavaScript
【Azure 环境】前端Web通过Azure AD获取Token时发生跨域问题(CORS Error)
【Azure 环境】前端Web通过Azure AD获取Token时发生跨域问题(CORS Error)
|
1月前
|
JSON 小程序 API
【Azure API 管理】APIM CORS策略设置后,跨域请求成功和失败的Header对比实验
【Azure API 管理】APIM CORS策略设置后,跨域请求成功和失败的Header对比实验
|
2月前
|
安全
CORS 跨域资源共享的实现原理
CORS 跨域资源共享的实现原理