CORS跨域资源共享(三):@CrossOrigin/CorsFilter处理跨域请求示例及原理分析【享学Spring MVC】(下)

简介: CORS跨域资源共享(三):@CrossOrigin/CorsFilter处理跨域请求示例及原理分析【享学Spring MVC】(下)
@CrossOrigin初始化


关于此注解的初始化,在完成mapping注册的时候就已经完成了,大致步骤如下:


AbstractHandlerMethodMapping:
  // 注册一个mapping
  public void registerMapping(T mapping, Object handler, Method method) {
    this.mappingRegistry.register(mapping, handler, method);
  }
  // 内部类
  class MappingRegistry {
    // 记录着没一个HandlerMethod所对应的注解配置
    private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
    ...
    public void register(T mapping, Object handler, Method method) {
      ...
      // initCorsConfiguration这里就是解析handler上面的注解喽~~~
      // 此init方法只有RequestMappingHandlerMapping子类重写了~~~
      CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
      if (corsConfig != null) { // 若不为null(有注解配置),就缓存起来
        this.corsLookup.put(handlerMethod, corsConfig);
      }
      ...
    }
  }


对于handler上次注解的解析,最终是由RequestMappingHandlerMapping完成的:


它显著的特点是:和Handler强绑定,因此在注册Mapping的时候就完成初始化工作。


综上所述可得出这三种配置方式的区别:


  1. CorsFilter方式:完全独立的Filter,和其它配置并不冲突和也无关联,最终委托给CorsProcessor来完成的
  2. addCorsMappings方式:它的配置会作用于所有的内置配置的HandlerMapping上,所以它就是global全局配置
  3. @CrossOrigin方式:它和某一个具体的handler强绑定,所以它属于局部配置。


说明:方式2和方式3可以形成互补配置,有combine的效果。


为何OPTIONS请求进入不了Controller的Handler方法内?


这个问题是系列文章的第一篇我抛出来的,因为有一个现象是:简单请求我可以在Controller的方法内向response手动添加请求头搞定。但是非简单请求这么做行不通了,原因是OPTIONS请求根本进入不了方法体~


阅读完本文的上半拉,此问题的答案就显而易见了,因此我此处不再废话。倘若对此问题还没想到答案的小伙伴,欢迎你在下面给我留言我会及时解答你的。


为何给response设置响应头写在postHandle()方法内无效?


这个问题倒是困扰了我好一会,直到我直到了Spring MVC对它的处理过程。

问题的现象是:response的响应头都有,但http状态码却是403,跨域失败。结果如下截图:


image.png


针对此问题作出如下解释供以参考:


  1. 上面有说到一句话:匹配上handler后,若是OPTIONS请求的话,它最终的handler不是原handler而是一个全新的PreFlightHandler处理器,并且并且并且chain上的拦截器们都是会生效的。
  2. 关键就在这里:PreFlightHandler执行handler处理方法最终是委托给CorsProcessor执行的,若config == null并且是 预检请求 ,那它就会执行:rejectRequest(serverResponse),这时状态码就已经设置为了403了,因此等handler方法执行完成之后再执行postHandle()方法体,因为返回状态码已经设置好,已经无力回天了,so就出现了如此怪异现象~


有人说在postHandle()方法里加上这么一句,手动把响应码改成200:response.setStatus(HttpStatus.OK.value());。

效果:能达到想要的跨域效(真实请求能继续发送)。但是我强烈不建议你这么去做,因此这样你需要加很多逻辑判断(什么时候应该设置,什么时候不应该),得不偿失。


DispatcherServlet.doOptions()方法简单分析

说明:dispatchOptionsRequest这个参数虽然默认值是false,但在DispatcherServlet所有的构造器里都有这么一句:setDispatchOptionsRequest(true)。


FrameworkServlet:
  /** Should we dispatch an HTTP OPTIONS request to {@link #doService}?. */
  private boolean dispatchOptionsRequest = false;
  @Override
  protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 若dispatchOptionsRequest = true 或者是预检请求OPTIONS请求,都会processRequest
    // processRequest(request, response);就是复杂的视图渲染逻辑~~~
    if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
      processRequest(request, response);
      // 若你自己设置了allow响应头,那就不处理了。否则交给下面处理
      if (response.containsHeader("Allow")) {
        // Proper OPTIONS response coming from a handler - we're done.
        return;
      }
    }
    // Use response wrapper in order to always add PATCH to the allowed methods
    // 开发者自己没有设置Allow这个响应头就会进这里来,最终效果是
    // Allow:GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
    super.doOptions(request, new HttpServletResponseWrapper(response) {
      @Override
      public void setHeader(String name, String value) {
        if ("Allow".equals(name)) {
          value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();
        }
        super.setHeader(name, value);
      }
    });
  }


若CORS请求的URL不存在,响应码404还是403?


  • 无默认的servlet处理器(DefaultServletHandler):404(找不到对应的handler)
  • 有默认的servlet处理器:403(能找到handler,因为有默认的处理器兜底嘛)


Spring MVC的这个配置用于开启默认处理器与否:


    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        //configurer.enable();
        //configurer.enable("default");
    }


总结


@CrossOrigin关注方法,CorsFilter等其它方式更关注URL。

该CORS系列通过三篇文章层层递进的讲述了CORS跨域请求访问自己如何处理等相关议题,在前后端分离开发模式的今天我觉得后端程序员有必要掌握这块内容,因此特撰文分享给大家,我觉得应该是能对很多人有较大帮助的,所你还有别的想法,欢迎你给我留言~


相关文章
|
10月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
1102 22
|
10月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
2976 0
|
9月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
9月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
9月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
782 2
|
11月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
1224 1
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
465 0
|
12月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
832 0
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
368 0
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
952 0