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设置上一些头信息~~~

相关文章
|
4月前
|
API 数据安全/隐私保护 数据格式
API 资源详解:从概念到实战的完整指南
本文深入解析了 API 资源的概念、特征与设计原则,涵盖 RESTful 规范、资源分类、层级结构及实际应用示例。内容还包括版本管理、安全策略与性能优化技巧,帮助开发者构建高效、可维护的 API 系统。
367 115
|
1月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
2月前
|
智能设计 Java 测试技术
Spring中最大化@Lazy注解,实现资源高效利用
本文深入探讨了 Spring 框架中的 `@Lazy` 注解,介绍了其在资源管理和性能优化中的作用。通过延迟初始化 Bean,`@Lazy` 可显著提升应用启动速度,合理利用系统资源,并增强对 Bean 生命周期的控制。文章还分析了 `@Lazy` 的工作机制、使用场景、最佳实践以及常见陷阱与解决方案,帮助开发者更高效地构建可扩展、高性能的 Spring 应用程序。
127 0
Spring中最大化@Lazy注解,实现资源高效利用
|
8月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
476 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
8月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
725 0
|
8月前
|
前端开发 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
472 0
|
8月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
417 0
|
8月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RestController
本文主要介绍 Spring Boot 中 MVC 开发常用的几个注解及其使用方式,包括 `@RestController`、`@RequestMapping`、`@PathVariable`、`@RequestParam` 和 `@RequestBody`。其中重点讲解了 `@RestController` 注解的构成与特点:它是 `@Controller` 和 `@ResponseBody` 的结合体,适用于返回 JSON 数据的场景。文章还指出,在需要模板渲染(如 Thymeleaf)而非前后端分离的情况下,应使用 `@Controller` 而非 `@RestController`
338 0
|
4月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
324 0
|
4月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
153 0