spring MVC cors跨域实现源码解析
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跨域实现源码解析相关推荐
- 从零开始学 Java - Spring MVC 实现跨域资源 CORS 请求
论职业的重要性 问:为什么所有家长都希望自己的孩子成为公务员? 答:体面.有权.有钱又悠闲. 问:为什么所有家长都希望自己的孩子成为律师或医生? 答:体面.有钱.有技能. 问:为什么所有家长都不怎么知 ...
- Spring MVC 的跨域解决方案
点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 王森 来源 | cnblogs.com/wan ...
- Spring Boot CORS跨域资源共享实现方案
同源策略 同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能 同源策略限制cookie 等信息的跨源网页读取,可以保护本地用户信息 同源策略限制跨域 aja ...
- jQuery中Ajax+Spring MVC实现跨域请求
项目开发中,某个可独立.也可集成的子业务模块须要向外开放相关API接口,先说下项目本身使用了jersery来实现RESTful webservice以名词形式公布API.有意思的是在实际的操作中同事却 ...
- 详述 Spring MVC 启动流程及相关源码分析
文章目录 Web 应用部署初始化过程(Web Application Deployement) Spring MVC 启动过程 Listener 的初始化过程 Filter 的初始化 Servlet ...
- Spring MVC 项目 JSP 页面显示源码
问题描述:Spring MVC 项目的 JSP 页面跳转显示源码,而非页面 原因:Spring MVC 项目的前端拦截器 拦截了 JSP 页面 改正:去掉 * 插曲:如果仅仅改动 JSP 头文件为 H ...
- Spring提取@Transactional事务注解的源码解析
声明:本文是自己在学习spring注解事务处理源代码时所留下的笔记: 难免有错误,敬请读者谅解!!! 1.事务注解标签 <tx:annotation-driven /> 2.tx 命名空间 ...
- Spring事务管理的底层逻辑—源码解析
本文代码为spring 5.1.2 spring是如何控制事务的提交和回滚 加上@Transactional注解之后,Spring可以启到事务控制的功能了,再正式执行方法前它会做一些操作,我们来看看 ...
- Spring Security Core 5.1.2 源码解析 -- PasswordEncoderFactories
概述 PasswordEncoderFactories是Spring Security创建DelegatingPasswordEncoder对象的工厂类.该工厂所创建的DelegatingPasswo ...
最新文章
- keras module 'tensorflow' has no attribute 'placeholder'
- 在react-router中进行代码拆分
- POJ 1077 Eight
- 多级队列调度算法可视化界面_C++实现操作系统调度算法(FSFS,SJF,RR,多级反馈队列算法)...
- 使用Shell和Java驱动程序的MongoDB身份验证配置示例
- php可以写无缝轮播图吗,怎样用css实现无缝轮播图切换?
- MQ消息队列概述及主流MQ分析
- 微波暗室——天线方向图测试
- png在线转换icns
- awesome-python(python集合框架)
- Linux 下恢复误删文件
- Unity内置Shader解读1——Bumped Diffuse
- Java - IO流学习笔记
- 全机房最蒟蒻的讲堂_第一期_关于orz
- 《Head First 系列图书》大集合,附案例分析,免积分下载
- CSS实现当鼠标移入或者移出时实现动画过渡效果
- java 使用Apache PDFBox 对 PDF 文件进行剪裁
- 函数:fopen的使用方法
- python删除空值的行_python删除列为空的行的实现方法
- 多线程(一) 线程概念及创建线程的方法
热门文章
- Linux设置qt-android开发环境
- Windows Server 2016 Technical Preview 5 X64 中文版下载地址
- topcoder srm 330 div1
- Android上的MVP:如何组织显示层的内容
- 在ActionBar显示ShareActionProvider分享文本,点击可以打开进行分享(19)
- Linux 网络编程(TCP)
- 微寻,把“线下医院”带到“线上轻松问诊”
- linux 条件判断
- [zz]ZeroMQ 的模式
- .NET 2.0 泛型在实际开发中的一次小应用