前言
上篇文章通过我模拟的跨域请求实例和结果分析,相信小伙伴们都已经80%的掌握了CORS到底是怎么一回事以及如何使用它。由于Java语言中的web框架几乎都是使用的Spring MVC,因此本文将聚焦于Spring MVC对CORS的支持,深度分析下它对CORS支持的相关API,这也方便下一章节的灵活使用以及流程原理分析。
Spring MVC与CORS
Spring MVC一直到4.2版本“才”开始内置对CORS支持,至于为何到这个版本Spring官方才对此提供支持,我这里需要结合时间轴来给大家解释一下。
上文我有说到了CORS它属于W3C的标准。我们知道任何一个规范的形成都是非常漫长的。W3C对web标准的制定分为如下7个阶段(从上到下有序):
- WD(Working Draft 工作草案):不稳定也不完整
- CR(Candidate Recommendation 候选推荐标准):所有的已知issues都被解决了
- PR(Proposed Recommendation 提案推荐标准):在浏览器做各种测试,此部分不会再有实质性的改动
- PER(Proposed Edited Recommendation 已修订的提案推荐标准):
- REC(Recommendation 推荐标准,通常称之为 standard,即事实标准):几乎不会再变动任何东西
- RET(Retired 退役的):最后这两个是建立在REC基础上变来,成熟的技术一般都不会有后面这两个
- NOTE(Group Note 工作组说明):
关于这7步,从这里 可以看倒CORS的WD从2009-03-17开始,2014-01-16进入的REC阶段,可谓正式毕业。而Spring4.2是在2015-06发布给与的全面支持,从时间轴上看Spring的响应速度还是把握得不错的(毕竟CORS经历过一段时间市场的考验Spring才敢全面纳入进来支持嘛~)
Tips:在Spring4.2之前,官方没有提供内置的支持,所以那时都是自己使用Filter/拦截器来处理。它的唯一缺点就是可能没那么灵活和优雅,后续官方提供标注支持后能力更强更为灵活了(底层原理都一样)
Spring MVC中CORS相关类及API说明
所有涉及到和CORS相关的类、注解、代码片段都是Spring4.2后才有的,请保持一定的版本意识。
从截图里可以看出spring-web包提供的专门用于处理CORS的相关的类,下面有必要进行逐个分析
CorsConfiguration
它代表一个cors配置,记录着各种配置项。它还提供了检查给定请求的实际来源、http方法和头的方法供以调用。用人话说:它就是具体封装跨域配置信息的pojo。
默认情况下新创建的CorsConfiguration它是不允许任何跨域请求的,需要你手动去配置,或者调用applyPermitDefaultValues()开启GET、POST、Head的支持~
几乎所有场景,创建完CorsConfiguration最后都调用了applyPermitDefaultValues()方法。也就是说你不干预的情况下,一个CorsConfiguration配置一般都是支持GET、POST、Head的
// @since 4.2 public class CorsConfiguration { // public的通配符:代表所有的源、方法、headers... // 若你需要使用通配符,可以使用此静态常量 public static final String ALL = "*"; private static final List<HttpMethod> DEFAULT_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.GET, HttpMethod.HEAD)); // 默认许可所有方法 private static final List<String> DEFAULT_PERMIT_ALL = Collections.unmodifiableList(Arrays.asList(ALL)); // 默认许可这三个方法 private static final List<String> DEFAULT_PERMIT_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name())); // ==========把这些属性对应上文讲述的响应头们对应,和W3C标注都是对应上的========= @Nullable private List<String> allowedOrigins; @Nullable private List<String> allowedMethods; @Nullable private List<HttpMethod> resolvedMethods = DEFAULT_METHODS; @Nullable private List<String> allowedHeaders; @Nullable private List<String> allowedHeaders; @Nullable private List<String> exposedHeaders; @Nullable private Boolean allowCredentials; @Nullable private Long maxAge; ... // 省略所有构造函数以及所有的get/set方法 // 使用此方法将初始化模型翻转为以允许get、head和post请求的所有跨源请求的打开默认值开始 // 注意:此方法不会覆盖前面set进去的值,所以建议此方法可以作为兜底调用。实际上Spring内部也是用它兜底的 public CorsConfiguration applyPermitDefaultValues() { if (this.allowedOrigins == null) { this.allowedOrigins = DEFAULT_PERMIT_ALL; } if (this.allowedMethods == null) { this.allowedMethods = DEFAULT_PERMIT_METHODS; this.resolvedMethods = DEFAULT_PERMIT_METHODS.stream().map(HttpMethod::resolve).collect(Collectors.toList()); } if (this.allowedHeaders == null) { this.allowedHeaders = DEFAULT_PERMIT_ALL; } if (this.maxAge == null) { this.maxAge = 1800L; } return this; } public CorsConfiguration combine(@Nullable CorsConfiguration other) { ... } // 根据配置的允许来源检查请求的来源 // 返回值并不是bool值,而是字符串--> 返回可用的origin。若是null表示请求的origin不被支持 @Nullable public String checkOrigin(@Nullable String requestOrigin) { ... } // 检查预检请求的Access-Control-Request-Method这个请求头 public List<HttpMethod> checkHttpMethod(@Nullable HttpMethod requestMethod) { ... } // 检查预检请求的Access-Control-Request-Headers @Nullable public List<String> checkHeaders(@Nullable List<String> requestHeaders) { }
这个POJO的配置,是servlet传统web以及reactive web所共用的,它提供有校验的基本方法。它的属性、校验原则和W3C的CORS标准所对应。
CorsConfigurationSource
它表示一个源,该接口主要是为请求提供一个CorsConfiguration。
public interface CorsConfigurationSource { // 找到此request的一个CORS配置 @Nullable CorsConfiguration getCorsConfiguration(HttpServletRequest request); }
此接口方法的调用处有三个地方:
- AbstractHandlerMapping.getHandler()/getCorsConfiguration()
- CorsFilter.doFilterInternal()
- HandlerMappingIntrospector.getCorsConfiguration()
因为它可以根据request返回一个CORS配置。可以把这个接口理解为:存储request与跨域配置信息的容器。它的继承树如下:
首先需要说的便是cors包下的UrlBasedCorsConfigurationSource
UrlBasedCorsConfigurationSource
它位于org.springframework.web.cors包:它里面存储着path patterns和CorsConfiguration的键值对。
// @since 4.2 public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource { // 请务必注意:这里使用的是LinkedHashMap private final Map<String, CorsConfiguration> corsConfigurations = new LinkedHashMap<>(); private PathMatcher pathMatcher = new AntPathMatcher(); private UrlPathHelper urlPathHelper = new UrlPathHelper(); ... // 生路所有的get/set方法 // 这里的path匹配用到的是AntPathMatcher.match(),默认是按照ant风格进行匹配的 @Override @Nullable public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) { if (this.pathMatcher.match(entry.getKey(), lookupPath)) { return entry.getValue(); } } return null; } }
本类它是作为AbstractHandlerMapping(RequestMappingHandlerMapping)的默认跨域资源配置的管理类