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跨域请求访问自己如何处理等相关议题,在前后端分离开发模式的今天我觉得后端程序员有必要掌握这块内容,因此特撰文分享给大家,我觉得应该是能对很多人有较大帮助的,所你还有别的想法,欢迎你给我留言~


相关文章
|
11月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
1118 22
|
11月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
2998 0
|
10月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
10月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
793 2
|
12月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
1247 1
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
486 0
|
前端开发 Java 数据库连接
Spring核心原理剖析与解说
每个部分都是将一种巨大并且复杂的技术理念传达为更易于使用的接口,而这就是Spring的价值所在,它能让你专注于开发你的应用,而不必从头开始设计每一部分。
379 32
|
监控 架构师 NoSQL
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
|
Java 开发者 Spring
Spring框架 - 深度揭秘Spring框架的基础架构与工作原理
所以,当你进入这个Spring的世界,看似一片混乱,但细看之下,你会发现这里有个牢固的结构支撑,一切皆有可能。不论你要建设的是一座宏大的城堡,还是个小巧的花园,只要你的工具箱里有Spring,你就能轻松搞定。
546 9
|
安全 前端开发 Java
Spring Boot 项目中触发 Circular View Path 错误的原理与解决方案
在Spring Boot开发中,**Circular View Path**错误常因视图解析与Controller路径重名引发。当视图名称(如`login`)与请求路径相同,Spring MVC无法区分,导致无限循环调用。解决方法包括:1) 明确指定视图路径,避免重名;2) 将视图文件移至子目录;3) 确保Spring Security配置与Controller路径一致。通过合理设定视图和路径,可有效避免该问题,确保系统稳定运行。
960 0