spring MVC cors跨域实现源码解析
# spring MVC cors跨域实现源码解析
> 名词解释:跨域资源共享(Cross-Origin Resource Sharing)
简单说就是只要协议、IP、http方法任意一个不同就是跨域。
spring MVC自4.2开始添加了跨域的支持。
跨域具体的定义请移步[mozilla](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS)查看
## 使用案例
spring mvc中跨域使用有3种方式:
在web.xml中配置CorsFilter
```xml
cors
org.springframework.web.filter.CorsFilter
cors
/*
```
在xml中配置
```xml
// 简单配置,未配置的均使用默认值,就是全面放开
// 这是一个全量配置
```
使用注解
```java
@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。
属性都是多值组合使用的。
```java
// CorsConfiguration
public static final String ALL = "*";
// 允许的请求源
private List allowedOrigins;
// 允许的http方法
private List allowedMethods;
// 允许的请求头
private List allowedHeaders;
// 返回的响应头
private List exposedHeaders;
// 是否允许携带cookies
private Boolean allowCredentials;
// 预请求的存活有效期
private Long maxAge;
```
combine是将跨域信息进行合并
3个check方法分别是核对request中的信息是否包含在允许范围内
### 配置初始化
在系统启动时通过CorsBeanDefinitionParser解析配置文件;
加载RequestMappingHandlerMapping时,通过InitializingBean的afterProperties的钩子调用initCorsConfiguration初始化注解信息;
#### 配置文件初始化
在CorsBeanDefinitionParser类的parse方法中打一个断点。


CorsBeanDefinitionParser的调用栈
通过代码可以看到这边解析中的定义信息。
跨域信息的配置可以以path为单位定义多个映射关系。
解析时如果没有定义则使用默认设置
```java
// 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然后再实例化。
```java
// MvcNamespaceUtils
public static RuntimeBeanReference registerCorsConfigurations(Map 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
```java
// RequestMappingHandlerMapping
@Override
protected 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。
```java
// UrlBasedCorsConfigurationSource
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for(Map.Entry entry : this.corsConfigurations.entrySet()) {
if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
return entry.getValue();
}
}
return null;
}
// AbstractHandlerMapping
public 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;
}
// HttpHeaders
public static final String ORIGIN = "Origin";
// CorsUtils
public static boolean isCorsRequest(HttpServletRequest request) {
return (request.getHeader(HttpHeaders.ORIGIN) != null);
}
```
通过请求头的http方法是否options判断是否预请求,如果是使用PreFlightRequest替换处理器;如果是普通请求,添加一个拦截器CorsInterceptor。
PreFlightRequest是CorsProcessor对于HttpRequestHandler的一个适配器。这样HandlerAdapter直接使用HttpRequestHandlerAdapter处理。
CorsInterceptor 是CorsProcessor对于HnalderInterceptorAdapter的适配器。
```java
// AbstractHandlerMapping
protected 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;
}
@Override
public 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;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
return corsProcessor.processRequest(this.config, request, response);
}
}
// CorsUtils
public static boolean isPreFlightRequest(HttpServletRequest request) {
return (isCorsRequest(request) && request.getMethod().equals(HttpMethod.OPTIONS.name()) &&
request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null);
}
```
https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
spring MVC cors跨域实现源码解析的更多相关文章
- spring MVC cors跨域实现源码解析 CorsConfiguration UrlBasedCorsConfigurationSource
spring MVC cors跨域实现源码解析 spring MVC cors跨域实现源码解析 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议 ...
- Spring MVC CORS 跨域
介绍 跨域CORS,全称是"跨域资源共享"(Cross-origin resource sharing) 当页面发出跨域请求时: 简单请求(先简单理解为正常的get/post吧): ...
- 从零开始学 Java - Spring MVC 实现跨域资源 CORS 请求
论职业的重要性 问:为什么所有家长都希望自己的孩子成为公务员? 答:体面.有权.有钱又悠闲. 问:为什么所有家长都希望自己的孩子成为律师或医生? 答:体面.有钱.有技能. 问:为什么所有家长都不怎么知 ...
- Java - Spring MVC 实现跨域资源 CORS 请求
拦截器设置响应头 这种方式原理就是利用拦截器在方法执行前,我们增加请求的响应头,用来支持跨域请求.这种方案是可行的,大部分都是采用这种方案.我当时也是打算采用这种方案,直到我发现原来 Spring 框 ...
- Spring MVC 实现跨域资源 CORS 请求
说到 AJAX 跨域,很多人最先想到的是 JSONP.的确,JSONP 我们已经十分熟悉,也使用了多年,从本质上讲,JSONP 的原理是给页面注入一个 <script>,把远程 JavaS ...
- spring mvc的跨域解决方案
什么是跨域 一句话:同一个ip.同一个网络协议.同一个端口,三者都满足就是同一个域,否则就是跨域. 为什么非得跨域 基于两个方面: a. web应用本身是部署在不同的服务器上 b.基于开发的角度 -- ...
- Ajax+Spring MVC实现跨域请求(JSONP)(转)
背景: AJAX向后台(springmvc)发送请求,报错:已阻止交叉源请求:同源策略不允许读取 http://127.0.0.1:8080/DevInfoWeb/getJsonp 上的远程资源.可 ...
- Ajax+Spring MVC实现跨域请求(JSONP)
背景: AJAX向后台(springmvc)发送请求,报错:已阻止交叉源请求:同源策略不允许读取 http://127.0.0.1:8080/DevInfoWeb/getJsonp 上的远程资源.可 ...
- spring mvc 解决跨域问题
Spring MVC 从4.2版本开始增加了对CORS的支持. 在Controller上使用@CrossOrigin注解: // 指定域名 @CrossOrigin("http://doma ...
随机推荐
- CodeForces 620B Grandfather Dovlet’s calculator
水题 #include<cstdio> #include<cstring> #include<cmath> #include<stack> #inclu ...
- UIView Methods
UIView翻译 (参考) 2011年04月12日 星期二 10:09 转载于:http://blog.csdn.net/tracylife/archive/2010/08/27/5842723.as ...
- (中等) HDU 4370 0 or 1,建模+Dijkstra。
Description Given a n*n matrix C ij (1<=i,j<=n),We want to find a n*n matrix X ij (1<=i,j&l ...
- shell之crontab
一 crontab高级用法 Crontab 主要是在做排程, 通常一般寫法大概都是如下: 0 0 * * * /usr/local/www/awstats/cgi-bin/awstats.sh 這種 ...
- Knockout 系列
由于近期要做的项目要兼容到IE6,原来的angular框架已经不能满足现在的兼容性要求. 解决方案: 1. 放弃框架,改为库 2. UI库采用 Knockout,css 3. css库 采用 boot ...
- Linux SSL 双向认证 浅解
请求方的操作:此步骤是为了验证CA的发证过程. 1.生成私钥: Openssl genrsa 1024 > private.key 生成私钥并保存到private.key文件中 ...
- 初步了解php,实现注册及登录
直接上图(数据库是用wamp实现的) 注册页 html 及 php 代码 登陆页 html 及 php 代码
- IP头,TCP头,UDP头,MAC帧头定义(转)
源:IP头,TCP头,UDP头,MAC帧头定义 一.MAC帧头定义 /*数据帧定义,头14个字节,尾4个字节*/ typedef struct _MAC_FRAME_HEADER { ]; //目的m ...
- Warning: Attempt to present on whose view is not in模态跳转问题
错误分析: controller A present controller B ,前提是A的view要存在,如果不存在,就会报这个错. 解决方法: 将 ...
- 实现基本TCP套接字客户端
//实现基本TCP套接字客户端var net = require('net');function getConnection(connName){ var client = net.connect({ ...