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天前
|
前端开发 API 数据安全/隐私保护
Web前端开发中的跨域资源共享(CORS)解决方案
【2月更文挑战第5天】在Web前端开发中,跨域资源共享(CORS)是一个常见的挑战。本文将探讨CORS的概念和原理,并介绍一些常用的解决方案,包括服务器端配置和前端处理方法,帮助开发者更好地应对跨域请求问题。
25 4
|
5天前
|
前端开发 开发者
前端开发中的跨域资源共享(CORS)解决方案探讨
【2月更文挑战第2天】跨域资源共享(CORS)是前端开发中常见的问题,本文将深入探讨CORS的原理及解决方案,包括简单请求、预检请求以及常用的CORS解决方案,为前端开发者提供深入的理解和应对CORS问题的有效方法。
11 1
|
4天前
|
前端开发 JavaScript API
探索前端开发中的跨域资源共享(CORS)
【2月更文挑战第3天】在前端开发中,跨域资源共享(CORS)是一个至关重要的话题。本文将深入探讨CORS的概念、工作原理以及如何在前端项目中正确配置和处理跨域请求,帮助开发者更好地理解和应用CORS技术。
20 7
|
4天前
|
前端开发 安全 JavaScript
前端开发中的跨域资源共享(CORS)机制
【2月更文挑战第3天】 在前端开发中,跨域资源共享(CORS)机制是一个重要的安全性问题。本文将介绍CORS的概念、原理和实现方式,并探讨在前端开发中如何处理跨域资源请求,以及如何提高网站的安全性。
|
2月前
|
缓存 前端开发 Java
13:SpringBoot跨域解决方案-Java Spring
13:SpringBoot跨域解决方案-Java Spring
38 0
|
1月前
|
Java 数据库 数据安全/隐私保护
基于SSM框架实现管科类考研自我管理系统(分前后台spring+springmvc+mybatis+maven+jsp+jquery)
基于SSM框架实现管科类考研自我管理系统(分前后台spring+springmvc+mybatis+maven+jsp+jquery)
|
2月前
|
JSON Java API
Spring Boot 无侵入式 实现API接口统一JSON格式返回
Spring Boot 无侵入式 实现API接口统一JSON格式返回
|
4天前
|
前端开发 数据安全/隐私保护 UED
探索前端开发中的跨域资源共享(CORS)
【2月更文挑战第3天】在当今Web开发中,跨域资源共享(CORS)扮演着至关重要的角色。本文将深入探讨CORS在前端开发中的作用和实践经验,带你解锁跨域访问的奥秘。
|
7天前
|
Java
【Java专题_03】spring-boot跨域问题如何解决
【Java专题_03】spring-boot跨域问题如何解决
10 0
|
20天前
|
Java API Maven
Springboot快速搭建跨域API接口(idea社区版2023.1.4+apache-maven-3.9.3-bin)
Springboot快速搭建跨域API接口(idea社区版2023.1.4+apache-maven-3.9.3-bin)
21 0

相关产品

  • 云迁移中心