CORS跨域资源共享(二):详解Spring MVC对CORS支持的相关类和API【享学Spring MVC】(中)

简介: CORS跨域资源共享(二):详解Spring MVC对CORS支持的相关类和API【享学Spring MVC】(中)

HandlerMappingIntrospector


HandlerMapping内省器。它是一个帮助类用于从HandlerMapping里获取信息,这些信息用于服务特定的请求。@EnableWebMvc默认会把它放进容器里,开发者可以@Autowired拿来使用(框架内部木有使用)


这个类比较重要,Spring Cloud Netflix Zuul巧用它实现了一些功能~


// @since 4.3.1
public class HandlerMappingIntrospector implements CorsConfigurationSource, ApplicationContextAware, InitializingBean {
  @Nullable
  private ApplicationContext applicationContext;
  @Nullable
  private List<HandlerMapping> handlerMappings;
  ... // 生路一些构造函数、set方法
  // 1、Map<String, HandlerMapping> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, HandlerMapping.class, true, false)
  // 2、如果第一步获取到了Beans,sort()排序一下
  // 3、若没找到,回退到`DispatcherServlet.properties`这个配置文件里去找
  @Override
  public void afterPropertiesSet() {
    if (this.handlerMappings == null) {
      Assert.notNull(this.applicationContext, "No ApplicationContext");
      this.handlerMappings = initHandlerMappings(this.applicationContext);
    }
  }
  // 从这些HandlerMapping找到MatchableHandlerMapping
  // 若一个都木有,此方法抛出异常
  @Nullable
  public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception { ... }
  @Override
  @Nullable
  public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
    Assert.notNull(this.handlerMappings, "Handler mappings not initialized");
    HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
    for (HandlerMapping handlerMapping : this.handlerMappings) {
      HandlerExecutionChain handler = null;
      try {
        handler = handlerMapping.getHandler(wrapper);
      } catch (Exception ex) {
        // Ignore
      }
      if (handler == null) {
        continue;
      }
      // 拿到作用在此Handler上的所有的拦截器们:HandlerInterceptor
      // 若有拦截器实现了CorsConfigurationSource接口,那就返回此拦截器上的CORS配置源
      if (handler.getInterceptors() != null) {
        for (HandlerInterceptor interceptor : handler.getInterceptors()) {
          if (interceptor instanceof CorsConfigurationSource) {
            return ((CorsConfigurationSource) interceptor).getCorsConfiguration(wrapper);
          }
        }
      }
      // 若这个Handle本身(注意:并不是所有的handler都是一个方法,也可能是个类,所以也有可能是会实现接口的)
      // 就是个CorsConfigurationSource 那就以它的为准
      if (handler.getHandler() instanceof CorsConfigurationSource) {
        return ((CorsConfigurationSource) handler.getHandler()).getCorsConfiguration(wrapper);
      }
    }
    return null;
  }
}


这个自省器最重要的功能就是初始化的时候把所有的HandlerMapping都拿到了。这个处理逻辑和DispatcherServlet.initHandlerMappings是一样的,为何不提取成公用的呢???


它另外一个功能便是获取HttpServletRequest对应的CORS配置信息:


  1. 从作用在此Handler的拦截器HandlerInterceptor上获取
  2. 若拦截器里木有,那就从Handler本身获取(若实现了CorsConfigurationSource接口)
  3. 都没有就返回null


CorsInterceptor


Cors拦截器。它最终会被放到处理器链HandlerExecutionChain里,用于拦截处理(最后一个拦截)。


  private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {
    @Nullable
    private final CorsConfiguration config;
    //拦截操作 最终是委托给了`CorsProcessor`,也就是DefaultCorsProcessor去完成处理的
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      return corsProcessor.processRequest(this.config, request, response);
    }
    @Override
    @Nullable
    public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
      return this.config;
    }
  }


该拦截器是AbstractHandlerMapping的私有内部类,它会在每次getHandler()的时候放进去专门作用域当前跨域的请求,具体的流程在下个章节里有讲述。


PreFlightHandler


这个和上面的CorsInterceptor互斥,它最终也是委托给corsProcessor来处理请求,只是它是专门用于处理预检请求的。详见CORS请求处理流程部分。


CorsProcessor(重要)


它便是CORS真正处理器:用于接收请求和一个配置,然后更新Response:比如接受/拒绝

public interface CorsProcessor {
  // 根据所给的`CorsConfiguration`来处理请求
  boolean processRequest(@Nullable CorsConfiguration configuration, HttpServletRequest request, HttpServletResponse response) throws IOException;
}



它的唯一实现类是DefaultCorsProcessor


DefaultCorsProcessor


它遵循的是W3C标准实现的。Spring MVC中对CORS规则的校验,都是通过委托给 DefaultCorsProcessor实现的


// @since 4.2
public class DefaultCorsProcessor implements CorsProcessor {
  @Override
  @SuppressWarnings("resource")
  public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, HttpServletResponse response) throws IOException {
    // 若不是跨域请求,不处理
    // 这个判断极其简单:请求中是否有Origin请求头。有这个头就是跨域请求
    if (!CorsUtils.isCorsRequest(request)) {
      return true;
    }
    // response.getHeaders().getAccessControlAllowOrigin() != null
    // 若响应头里已经设置好了Access-Control-Allow-Origin这个响应头,此处理器也不管了
    ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
    if (responseHasCors(serverResponse)) {
      logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");
      return true;
    }
    // 即使你有Origin请求头,但是是同源的请求,那也不处理
    ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
    if (WebUtils.isSameOrigin(serverRequest)) {
      logger.trace("Skip: request is from same origin");
      return true;
    }
    // 是否是预检请求,判断标准如下:
    // 是跨域请求 && 是`OPTIONS`请求 && 有Access-Control-Request-Method这个请求头
    boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
    // 若config == null,分两种case:
    // 是预检请求but木有给config,那就拒绝:给出状态码403
    //   response.setStatusCode(HttpStatus.FORBIDDEN)
    //   response.getBody().write("Invalid CORS request".getBytes(StandardCharsets.UTF_8));
    if (config == null) {
      if (preFlightRequest) {
        rejectRequest(serverResponse);
        return false; // 告诉后面的处理器不用再处理了
      } else { // 虽然没给config,但不是预检请求(是真是请求,返回true)
        return true;
      }
    }
    // 真正的跨域处理逻辑~~~~
    // 它的处理逻辑比较简单,立即了W3C规范理解它起来非常简单,本文略
    // checkOrigin/checkMethods/checkHeaders等等方法最终都是委托给CorsConfiguration去做的
    return handleInternal(serverRequest, serverResponse, config, preFlightRequest);
  }
  ...
}


使用框架来处理跨域的好处便是:兼容性很强且灵活。它的处理过程如下:


1.若不是跨域请求,不处理(注意是return true后面拦截器还得执行呢)。若是跨域请求继续处理。(是否是跨域请求就看请求头是否有Origin这个头)


2.判断response是否有Access-Control-Allow-Origin这个响应头,若有说明已经被处理过,那本处理器就不再处理了


3.判断是否是同源:即使有Origin请求头,但若是同源的也不处理


4.是否配置了CORS规则,若没有配置:

   1. 若是预检请求,直接决绝403,return false

   2. 若不是预检请求,则本处理器不处理


5.正常处理CORS请求,大致是如下步骤:

   1. 判断 origin 是否合法

   2. 判断 method 是否合法

   3. 判断 header是否合法

   4. 若其中有一项不合法,直接决绝掉403并return false。都合法的话:就在response设置上一些头信息~~~

相关文章
|
2月前
|
Java API 开发者
在Spring Boot中集成Swagger API文档
在Spring Boot中集成Swagger API文档
|
2月前
|
Java API Spring
Spring Boot中的API版本控制
Spring Boot中的API版本控制
|
10天前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
22天前
|
前端开发 JavaScript
MVC中简单数据模型(M): Model类
MVC中简单数据模型(M): Model类
|
18天前
|
前端开发 Java Spring
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
37 0
|
2月前
|
开发框架 Java 测试技术
Spring Boot中的API文档生成
Spring Boot中的API文档生成
|
2月前
|
安全 Java API
构建基于Spring Boot的REST API安全机制
构建基于Spring Boot的REST API安全机制
|
2月前
|
Java API 开发者
Spring Boot与API Blueprint的集成
Spring Boot与API Blueprint的集成
|
2月前
|
JSON Java API
Spring Boot中使用OpenAPI生成API文档
Spring Boot中使用OpenAPI生成API文档
|
2月前
|
前端开发 JavaScript Java
使用Spring Boot实现跨域资源共享(CORS)
使用Spring Boot实现跨域资源共享(CORS)
下一篇
云函数