spring MVC cors跨域实现源码解析

名词解释:跨域资源共享(Cross-Origin Resource Sharing)

简单说就是只要协议、IP、http方法任意一个不同就是跨域。

spring MVC自4.2开始添加了跨域的支持。

跨域具体的定义请移步mozilla查看

使用案例

spring mvc中跨域使用有3种方式:

在web.xml中配置CorsFilter

<filter><filter-name>cors</filter-name><filter-class>org.springframework.web.filter.CorsFilter</filter-class>
</filter>
<filter-mapping><filter-name>cors</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

在xml中配置

// 简单配置,未配置的均使用默认值,就是全面放开
<mvc:cors>  <mvc:mapping path="/**" />
</mvc:cors> // 这是一个全量配置
<mvc:cors>  <mvc:mapping path="/api/**"  allowed-origins="http://domain1.com, http://domain2.com"  allowed-methods="GET, PUT"  allowed-headers="header1, header2, header3"  exposed-headers="header1, header2" allow-credentials="false"  max-age="123" />  <mvc:mapping path="/resources/**"  allowed-origins="http://domain1.com" />
</mvc:cors>  

使用注解

@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {  @CrossOrigin("http://domain2.com")  @RequestMapping("/{id}")  public Account retrieve(@PathVariable Long id) {  // ...  }
}  

涉及概念

  • CorsConfiguration 具体封装跨域配置信息的pojo

  • CorsConfigurationSource request与跨域配置信息映射的容器

  • CorsProcessor 具体进行跨域操作的类

  • 诺干跨域配置信息初始化类

  • 诺干跨域使用的Adapter

涉及的java类:

  • 封装信息的pojo

    CorsConfiguration

  • 存储request与跨域配置信息的容器

    CorsConfigurationSource、UrlBasedCorsConfigurationSource

  • 具体处理类

    CorsProcessor、DefaultCorsProcessor

  • CorsUtils

  • 实现OncePerRequestFilter接口的Adapter

    CorsFilter

  • 校验request是否cors,并封装对应的Adapter

    AbstractHandlerMapping、包括内部类PreFlightHandler、CorsInterceptor

  • 读取CrossOrigin注解信息

    AbstractHandlerMethodMapping、RequestMappingHandlerMapping

  • 从xml文件中读取跨域配置信息

    CorsBeanDefinitionParser

  • 跨域注册辅助类

    MvcNamespaceUtils

debug分析

要看懂代码我们需要先了解下封装跨域信息的pojo--CorsConfiguration

这边是一个非常简单的pojo,除了跨域对应的几个属性,就只有combine、checkOrigin、checkHttpMethod、checkHeaders。

属性都是多值组合使用的。

    // CorsConfigurationpublic static final String ALL = "*";// 允许的请求源private List<String> allowedOrigins;// 允许的http方法private List<String> allowedMethods;// 允许的请求头private List<String> allowedHeaders;// 返回的响应头private List<String> exposedHeaders;// 是否允许携带cookiesprivate Boolean allowCredentials;// 预请求的存活有效期private Long maxAge;

combine是将跨域信息进行合并

3个check方法分别是核对request中的信息是否包含在允许范围内

配置初始化

在系统启动时通过CorsBeanDefinitionParser解析配置文件;

加载RequestMappingHandlerMapping时,通过InitializingBean的afterProperties的钩子调用initCorsConfiguration初始化注解信息;

配置文件初始化

在CorsBeanDefinitionParser类的parse方法中打一个断点。

CorsBeanDefinitionParser的调用栈

通过代码可以看到这边解析中的定义信息。

跨域信息的配置可以以path为单位定义多个映射关系。

解析时如果没有定义则使用默认设置

// CorsBeanDefinitionParser
if (mappings.isEmpty()) {// 最简配置时的默认设置CorsConfiguration config = new CorsConfiguration();config.setAllowedOrigins(DEFAULT_ALLOWED_ORIGINS);config.setAllowedMethods(DEFAULT_ALLOWED_METHODS);config.setAllowedHeaders(DEFAULT_ALLOWED_HEADERS);config.setAllowCredentials(DEFAULT_ALLOW_CREDENTIALS);config.setMaxAge(DEFAULT_MAX_AGE);corsConfigurations.put("/**", config);
}else {// 单个mapping的处理for (Element mapping : mappings) {CorsConfiguration config = new CorsConfiguration();if (mapping.hasAttribute("allowed-origins")) {String[] allowedOrigins = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-origins"), ",");config.setAllowedOrigins(Arrays.asList(allowedOrigins));}// ...}

解析完成后,通过MvcNamespaceUtils.registerCorsConfiguratoions注册

这边走的是spring bean容器管理的统一流程,现在转化为BeanDefinition然后再实例化。

// MvcNamespaceUtilspublic static RuntimeBeanReference registerCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations, ParserContext parserContext, Object source) {if (!parserContext.getRegistry().containsBeanDefinition(CORS_CONFIGURATION_BEAN_NAME)) {RootBeanDefinition corsConfigurationsDef = new RootBeanDefinition(LinkedHashMap.class);corsConfigurationsDef.setSource(source);corsConfigurationsDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);if (corsConfigurations != null) {corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);}parserContext.getReaderContext().getRegistry().registerBeanDefinition(CORS_CONFIGURATION_BEAN_NAME, corsConfigurationsDef);parserContext.registerComponent(new BeanComponentDefinition(corsConfigurationsDef, CORS_CONFIGURATION_BEAN_NAME));}else if (corsConfigurations != null) {BeanDefinition corsConfigurationsDef = parserContext.getRegistry().getBeanDefinition(CORS_CONFIGURATION_BEAN_NAME);corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);}return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME);}

注解初始化

在RequestMappingHandlerMapping的initCorsConfiguration中扫描使用CrossOrigin注解的方法,并提取信息。

RequestMappingHandlerMapping_initCorsConfiguration

// RequestMappingHandlerMapping@Overrideprotected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {HandlerMethod handlerMethod = createHandlerMethod(handler, method);CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), CrossOrigin.class);CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);if (typeAnnotation == null && methodAnnotation == null) {return null;}CorsConfiguration config = new CorsConfiguration();updateCorsConfig(config, typeAnnotation);updateCorsConfig(config, methodAnnotation);// ... 设置默认值return config;}   

跨域请求处理

HandlerMapping在正常处理完查找处理器后,在AbstractHandlerMapping.getHandler中校验是否是跨域请求,如果是分两种进行处理:

  • 如果是预请求,将处理器替换为内部类PreFlightHandler

  • 如果是正常请求,添加CorsInterceptor拦截器

拿到处理器后,通过请求头是否包含Origin判断是否跨域,如果是跨域,通过UrlBasedCorsConfigurationSource获取跨域配置信息,并委托getCorsHandlerExecutionChain处理

UrlBasedCorsConfigurationSource是CorsConfigurationSource的实现,从类名就可以猜出这边request与CorsConfiguration的映射是基于url的。getCorsConfiguration中提取request中的url后,逐一验证配置是否匹配url。

    // UrlBasedCorsConfigurationSourcepublic 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;}// AbstractHandlerMappingpublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {Object handler = getHandlerInternal(request);// ...HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);if (CorsUtils.isCorsRequest(request)) {CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;}// HttpHeaderspublic static final String ORIGIN = "Origin";// CorsUtilspublic static boolean isCorsRequest(HttpServletRequest request) {return (request.getHeader(HttpHeaders.ORIGIN) != null);}

通过请求头的http方法是否options判断是否预请求,如果是使用PreFlightRequest替换处理器;如果是普通请求,添加一个拦截器CorsInterceptor。

PreFlightRequest是CorsProcessor对于HttpRequestHandler的一个适配器。这样HandlerAdapter直接使用HttpRequestHandlerAdapter处理。

CorsInterceptor 是CorsProcessor对于HnalderInterceptorAdapter的适配器。

    // AbstractHandlerMappingprotected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,HandlerExecutionChain chain, CorsConfiguration config) {if (CorsUtils.isPreFlightRequest(request)) {HandlerInterceptor[] interceptors = chain.getInterceptors();chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);}else {chain.addInterceptor(new CorsInterceptor(config));}return chain;}private class PreFlightHandler implements HttpRequestHandler {private final CorsConfiguration config;public PreFlightHandler(CorsConfiguration config) {this.config = config;}@Overridepublic void handleRequest(HttpServletRequest request, HttpServletResponse response)throws IOException {corsProcessor.processRequest(this.config, request, response);}}private class CorsInterceptor extends HandlerInterceptorAdapter {private final CorsConfiguration config;public CorsInterceptor(CorsConfiguration config) {this.config = config;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {return corsProcessor.processRequest(this.config, request, response);}}// CorsUtilspublic static boolean isPreFlightRequest(HttpServletRequest request) {return (isCorsRequest(request) && request.getMethod().equals(HttpMethod.OPTIONS.name()) &&request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null);}

可以去github查看: https://github.com/haplone/spring_doc/blob/master/mvc/cors.md

参考:
https://spring.io/blog/2015/06/08/cors-support-in-spring-framework

转载于:https://www.cnblogs.com/leftthen/p/6378090.html

spring MVC cors跨域实现源码解析相关推荐

  1. 从零开始学 Java - Spring MVC 实现跨域资源 CORS 请求

    论职业的重要性 问:为什么所有家长都希望自己的孩子成为公务员? 答:体面.有权.有钱又悠闲. 问:为什么所有家长都希望自己的孩子成为律师或医生? 答:体面.有钱.有技能. 问:为什么所有家长都不怎么知 ...

  2. Spring MVC 的跨域解决方案

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 王森 来源 | cnblogs.com/wan ...

  3. Spring Boot CORS跨域资源共享实现方案

    同源策略 同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能 同源策略限制cookie 等信息的跨源网页读取,可以保护本地用户信息 同源策略限制跨域 aja ...

  4. jQuery中Ajax+Spring MVC实现跨域请求

    项目开发中,某个可独立.也可集成的子业务模块须要向外开放相关API接口,先说下项目本身使用了jersery来实现RESTful webservice以名词形式公布API.有意思的是在实际的操作中同事却 ...

  5. 详述 Spring MVC 启动流程及相关源码分析

    文章目录 Web 应用部署初始化过程(Web Application Deployement) Spring MVC 启动过程 Listener 的初始化过程 Filter 的初始化 Servlet ...

  6. Spring MVC 项目 JSP 页面显示源码

    问题描述:Spring MVC 项目的 JSP 页面跳转显示源码,而非页面 原因:Spring MVC 项目的前端拦截器 拦截了 JSP 页面 改正:去掉 * 插曲:如果仅仅改动 JSP 头文件为 H ...

  7. Spring提取@Transactional事务注解的源码解析

    声明:本文是自己在学习spring注解事务处理源代码时所留下的笔记: 难免有错误,敬请读者谅解!!! 1.事务注解标签 <tx:annotation-driven /> 2.tx 命名空间 ...

  8. Spring事务管理的底层逻辑—源码解析

    本文代码为spring 5.1.2 spring是如何控制事务的提交和回滚 加上@Transactional注解之后,Spring可以启到事务控制的功能了,再正式执行方法前它会做一些操作,我们来看看 ...

  9. Spring Security Core 5.1.2 源码解析 -- PasswordEncoderFactories

    概述 PasswordEncoderFactories是Spring Security创建DelegatingPasswordEncoder对象的工厂类.该工厂所创建的DelegatingPasswo ...

最新文章

  1. keras module 'tensorflow' has no attribute 'placeholder'
  2. 在react-router中进行代码拆分
  3. POJ 1077 Eight
  4. 多级队列调度算法可视化界面_C++实现操作系统调度算法(FSFS,SJF,RR,多级反馈队列算法)...
  5. 使用Shell和Java驱动程序的MongoDB身份验证配置示例
  6. php可以写无缝轮播图吗,怎样用css实现无缝轮播图切换?
  7. MQ消息队列概述及主流MQ分析
  8. 微波暗室——天线方向图测试
  9. png在线转换icns
  10. awesome-python(python集合框架)
  11. Linux 下恢复误删文件
  12. Unity内置Shader解读1——Bumped Diffuse
  13. Java - IO流学习笔记
  14. 全机房最蒟蒻的讲堂_第一期_关于orz
  15. 《Head First 系列图书》大集合,附案例分析,免积分下载
  16. CSS实现当鼠标移入或者移出时实现动画过渡效果
  17. java 使用Apache PDFBox 对 PDF 文件进行剪裁
  18. 函数:fopen的使用方法
  19. python删除空值的行_python删除列为空的行的实现方法
  20. 多线程(一) 线程概念及创建线程的方法

热门文章

  1. Linux设置qt-android开发环境
  2. Windows Server 2016 Technical Preview 5 X64 中文版下载地址
  3. topcoder srm 330 div1
  4. Android上的MVP:如何组织显示层的内容
  5. 在ActionBar显示ShareActionProvider分享文本,点击可以打开进行分享(19)
  6. Linux 网络编程(TCP)
  7. 微寻,把“线下医院”带到“线上轻松问诊”
  8. linux 条件判断
  9. [zz]ZeroMQ 的模式
  10. .NET 2.0 泛型在实际开发中的一次小应用