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

相关文章
|
11月前
|
前端开发 Java API
利用 Spring WebFlux 技术打造高效非阻塞 API 的完整开发方案与实践技巧
本文介绍了如何使用Spring WebFlux构建高效、可扩展的非阻塞API,涵盖响应式编程核心概念、技术方案设计及具体实现示例,适用于高并发场景下的API开发。
741 0
|
人工智能 前端开发 JavaScript
webpack-dev-server代理后端一直报CORS跨域或500错误
在Vue项目中使用Webpack的devServer代理后端接口时,遇到500错误。问题根源在于浏览器请求中携带的Origin头导致服务器报错,而Postman测试正常。通过分析发现,调整或移除Origin头可解决问题。解决办法包括:1) 在代理配置中添加正确的Origin头;2) 删除请求中的Origin头。文章还深入解析了Origin头的作用及changeOrigin配置的实际意义,并附带相关文档链接,帮助开发者更好地理解与解决类似跨域问题。
920 20
|
9月前
|
安全 API PHP
PHP中实现CORS跨域资源共享的方法
通过这种方式,你可以在PHP应用中灵活地实现CORS,以支持跨域Web应用的需求。
620 15
|
10月前
|
人工智能 Java 机器人
基于Spring AI Alibaba + Spring Boot + Ollama搭建本地AI对话机器人API
Spring AI Alibaba集成Ollama,基于Java构建本地大模型应用,支持流式对话、knife4j接口可视化,实现高隐私、免API密钥的离线AI服务。
7275 2
基于Spring AI Alibaba + Spring Boot + Ollama搭建本地AI对话机器人API
|
前端开发 JavaScript 应用服务中间件
前端跨域问题解决Access to XMLHttpRequest at xxx from has been blocked by CORS policy
跨域问题是前端开发中常见且棘手的问题,但通过理解CORS的工作原理并应用合适的解决方案,如服务器设置CORS头、使用JSONP、代理服务器、Nginx配置和浏览器插件,可以有效地解决这些问题。选择合适的方法可以确保应用的安全性和稳定性,并提升用户体验。
9479 90
|
存储 人工智能 Java
Spring AI与DeepSeek实战四:系统API调用
在AI应用开发中,工具调用是增强大模型能力的核心技术,通过让模型与外部API或工具交互,可实现实时信息检索(如天气查询、新闻获取)、系统操作(如创建任务、发送邮件)等功能;本文结合Spring AI与大模型,演示如何通过Tool Calling实现系统API调用,同时处理多轮对话中的会话记忆。
2951 57
|
11月前
|
缓存 Java API
Spring WebFlux 2025 实操指南详解高性能非阻塞 API 开发全流程核心技巧
本指南基于Spring WebFlux 2025最新技术栈,详解如何构建高性能非阻塞API。涵盖环境搭建、响应式数据访问、注解与函数式两种API开发模式、响应式客户端使用、测试方法及性能优化技巧,助你掌握Spring WebFlux全流程开发核心实践。
1628 0
|
Java API 网络架构
基于 Spring Boot 框架开发 REST API 接口实践指南
本文详解基于Spring Boot 3.x构建REST API的完整开发流程,涵盖环境搭建、领域建模、响应式编程、安全控制、容器化部署及性能优化等关键环节,助力开发者打造高效稳定的后端服务。
1503 1
|
JSON 缓存 前端开发
对CORS(跨域)的一些见解
CORS(跨域资源共享)是W3C标准,用于解决AJAX跨源请求限制。浏览器与服务器需共同支持CORS,浏览器自动处理请求头,开发者无需额外操作。CORS分为简单请求与非简单请求:简单请求满足特定条件(如方法为GET/POST/HEAD且头信息有限制),浏览器直接发送;非简单请求需先进行“预检”请求(OPTIONS方法),确认服务器允许后才发送实际请求。服务器回应需包含Access-Control-Allow-Origin等字段,以控制跨域访问权限。
387 10
|
前端开发 IDE Java
Spring MVC 中因导入错误的 Model 类报错问题解析
在 Spring MVC 或 Spring Boot 开发中,若导入错误的 `Model` 类(如 `ch.qos.logback.core.model.Model`),会导致无法解析 `addAttribute` 方法的错误。正确类应为 `org.springframework.ui.Model`。此问题通常因 IDE 自动导入错误类引起。解决方法包括:删除错误导入、添加正确包路径、验证依赖及清理缓存。确保代码中正确使用 Spring 提供的 `Model` 接口以实现前后端数据传递。
522 0