昨天前端遇到跨域问题,但是这个问题,我应该在很早就配置过,后来发现,spring boot的配置有一点改变。
查找了一些资料,才发现是拦截器的顺序问题。
pring 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。
属性都是多值组合使用的。
// CorsConfiguration
public static final String ALL = "*";
// 允许的请求源
private List<String> allowedOrigins;
// 允许的http方法
private List<String> allowedMethods;
// 允许的请求头
private List<String> allowedHeaders;
// 返回的响应头
private List<String> exposedHeaders;
// 是否允许携带cookies
private 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然后再实例化。
// MvcNamespaceUtils
public 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
@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。
// UrlBasedCorsConfigurationSource
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
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的适配器。
// 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);
}
可以去github查看: https://github.com/haplone/spring_doc/blob/master/mvc/cors.md
参考:
https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
相关推荐
CORS 跨域是 Web 开发中一个非常重要的概念,Spring MVC 提供了多种方式来实现 CORS 跨域,包括使用 `@CrossOrigin` 注解和配置文件。通过使用 CORS 跨域,我们可以实现不同域名下的资源共享,提高 Web 应用程序的...
Spring MVC 与 CORS 跨域的详细介绍 本文对 Spring MVC 与 CORS 跨域的详细介绍,包括 CORS 的知识点和如何在 Spring MVC 中配置 CORS。 CORS 简介 同源策略(same origin policy)是浏览器安全的基石。在同源...
这篇博客“基于Spring和Spring MVC实现可跨域访问的REST服务”深入探讨了如何使用这两个流行的Java框架来创建这样的服务。Spring作为核心框架提供依赖注入和整体架构支持,而Spring MVC作为其Web模块,专门用于构建...
在 Spring 4.3 框架中,实现跨域 CORS 需要在 Controller 中添加 @CrossOrigin 注解,并在 Spring MVC 配置文件中添加 CORS 配置。 首先,在 Controller 中添加 @CrossOrigin 注解,以允许跨域请求。例如: ```java...
总结,CORS跨域配置是解决前后端跨域问题的关键,而在Java环境中,我们可以通过编写Filter或使用Spring MVC的注解来实现。配合jar文件,可以将这些配置部署到服务器,为Web应用提供跨域支持。理解并正确运用CORS,能...
本篇将详细讲解如何利用Spring解决跨域问题,以及如何使用`cors-filter-1.7.jar`和`java-property-utils-1.9.1.jar`这两个库来辅助实现。 一、Spring解决跨域问题的基本原理 1. CORS定义:跨域是指浏览器遵循同源...
注意,为了实现跨域预览,可能需要在服务器端添加CORS配置。 总结起来,结合Spring MVC的`multipartResolver`和uploadify,我们可以实现一个高效、用户友好的图片上传和预览功能。这涉及到后端文件上传的处理、前端...
Spring MVC提供了多种解决跨域问题的方法,其中两种常见的策略是使用CORS(Cross-Origin Resource Sharing)配置和Filter过滤器。 ### CORS配置 CORS是一种标准的W3C规范,它允许服务器放宽同源策略的限制。在...
SpringMVC 跨域解决方案 SpringMVC 跨域解决方案是指在 SpringMVC 框架中解决跨站 HTTP 请求(Cross-site HTTP request)的两种方案。跨域是指发起请求的资源所在域不同于请求指向资源所在域的 HTTP 请求。在前后端...
5. **数据绑定**:Dojo 控件如 DataGrid 可以通过 JSONP 或 CORS 从 Spring MVC 提供的 RESTful API 获取数据。确保服务器端支持跨域请求,或设置响应头以允许 JSONP 请求。 **四、DataGridTest 示例** 在你提供的...
2. 安全性:考虑跨域资源共享(CORS)策略,确保前后端通信的安全。 3. 数据交互:定义RESTful API接口,使用AngularJS的$http或$resource服务进行数据交换。 4. 路由管理:AngularJS的$routeProvider或ui-router...
7. **CORS支持** - 对于跨域资源共享(CORS),Spring MVC提供了`CorsRegistry`和`CorsConfiguration`来配置允许哪些来源进行访问。 8. **Validation** - 使用JSR-303/JSR-349提供的注解进行数据验证,例如`@Not...
本文将深入探讨Spring MVC如何解决跨域问题,并通过一个具体的例子来展示解决方案。 首先,理解什么是跨域。跨域是由于浏览器的同源策略(Same-origin policy)导致的一种安全机制。同源策略规定,JavaScript只能...
默认情况下,浏览器执行同源策略,阻止跨域请求,但CORS提供了安全地实现跨域数据交互的机制。 **过滤器(Filter)** `CrossDomainFilter.java` 是一个实现了Servlet Filter接口的类,用于处理HTTP请求和响应。在...
Spring实现处理跨域请求代码详解 Spring实现处理跨域请求代码详解是指在Spring框架中如何处理跨域请求,以便让客户端可以跨域访问...我们可以使用CORS协议来解决跨域问题,并使用Spring框架来实现处理跨域请求的功能。
解决方案是配置Spring MVC的CORS(Cross-Origin Resource Sharing)过滤器,允许特定的跨域请求。 2. **HTTP方法限制**:有些安全设置或浏览器插件可能会限制非GET请求。检查浏览器设置,确保POST请求未被禁用。 3...
在这个例子中,我们基于Spring MVC、Maven、WebService和Memcached来实现一个功能完善的单点登录系统,同时支持完全跨域和单点退出。 1. **Spring MVC**: Spring MVC是Spring框架的一个模块,主要用于构建Web应用...
Spring Boot提供了多种方式来实现跨域支持,其中最常用的是通过CORS(Cross-Origin Resource Sharing,跨源资源共享)机制。下面我们将详细探讨如何在Spring Boot中配置CORS,以及它与MyBatis框架的集成。 首先,...