首页 > 其他分享 >SpringMVC-CORS解决跨域

SpringMVC-CORS解决跨域

时间:2022-11-25 22:24:26浏览次数:29  
标签:return 跨域 SpringMVC request CORS null config response 请求

跨域的相关知识请参考https://blog.csdn.net/weixin_66375317/article/details/124545878。SpringMVC解决跨域的方法请参考https://blog.csdn.net/forlinkext/article/details/121267500。
SpringMVC可通过配置mvc:cors解决跨域。

	<mvc:cors>
	<mvc:mapping allowed-origins="*" path="/*"></mvc:mapping>
</mvc:cors>

allowed-origins表示允许的请求来源,path表示访问路径。

一、跨域环境搭建和解析mvc:cors
跨域一般在前后端分离中比较常见。新建html文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="http://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>
    <script type="text/javascript">
        function println(data) {
            console.log(data);
            console.log('print');
        }
      function jsonp_test() {
            $.ajax({
                type: "get",
                url: "http://localhost:8090/myself-web/corsTest",
                dataType: "text",
                success:function(result){
                console.log('success:',result)
                 
                },
                error:function(err){
                 console.log('错误:',err)
              
                },
            });
        }
    </script>
</head>
<body onl oad="jsonp_test()">
</body>
</html>

用浏览器打开页面时通过ajax请求后端服务。此时不是同一个端口,发生跨域。

mvc:cors标签由CorsBeanDefinitionParser解析。

CorsBeanDefinitionParser.parse(Element element, ParserContext parserContext)

public BeanDefinition parse(Element element, ParserContext parserContext) {

	Map<String, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
	List<Element> mappings = DomUtils.getChildElementsByTagName(element, "mapping");

	if (mappings.isEmpty()) {
		CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
		corsConfigurations.put("/**", config);
	}
	else {
		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));
			}
			if (mapping.hasAttribute("allowed-origin-patterns")) {
				String[] patterns = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-origin-patterns"), ",");
				config.setAllowedOriginPatterns(Arrays.asList(patterns));
			}
			if (mapping.hasAttribute("allowed-methods")) {
				String[] allowedMethods = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-methods"), ",");
				config.setAllowedMethods(Arrays.asList(allowedMethods));
			}
			if (mapping.hasAttribute("allowed-headers")) {
				String[] allowedHeaders = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-headers"), ",");
				config.setAllowedHeaders(Arrays.asList(allowedHeaders));
			}
			if (mapping.hasAttribute("exposed-headers")) {
				String[] exposedHeaders = StringUtils.tokenizeToStringArray(mapping.getAttribute("exposed-headers"), ",");
				config.setExposedHeaders(Arrays.asList(exposedHeaders));
			}
			if (mapping.hasAttribute("allow-credentials")) {
				config.setAllowCredentials(Boolean.parseBoolean(mapping.getAttribute("allow-credentials")));
			}
			if (mapping.hasAttribute("max-age")) {
				config.setMaxAge(Long.parseLong(mapping.getAttribute("max-age")));
			}
			config.applyPermitDefaultValues();
			config.validateAllowCredentials();
			corsConfigurations.put(mapping.getAttribute("path"), config);
		}
	}

	MvcNamespaceUtils.registerCorsConfigurations(
			corsConfigurations, parserContext, parserContext.extractSource(element));
	return null;
}

遍历mvc:cors下的mvc:mapping子标签。解析allowed-origins,allowed-origin-patterns,allowed-methods,allowed-headers,exposed-headers,allow-credentials,max-age等属性封装成CorsConfiguration。将mvc:mapping标签的path属性值为key,CorsConfiguration为value加到corsConfigurations中。applyPermitDefaultValues设置CorsConfiguration属性的默认值。

applyPermitDefaultValues()

public CorsConfiguration applyPermitDefaultValues() {
	if (this.allowedOrigins == null && this.allowedOriginPatterns == 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;
}

默认的允许源是DEFAULT_PERMIT_ALL(*),允许所有来源。默认的支持请求方法是GET,HEAD,POST。默认请求头是ALL。默认的最大age是1800秒。

CROS相关的OPTION方法预检请求请参考https://blog.csdn.net/small_cutey/article/details/125313478。

二、SpringMVC源码解析CORS
在RequestMappingHandlerMapping中获取handle时,即AbstractHandlerMethodMapping.lookupHandlerMethod(String lookupPath, HttpServletRequest request)查找HandlerMethod,如果找到了handle则调用addMatchingMappings(directPathMatches, matches, request);匹配的handle。最终会调用RequestMappingInfo.getMatchingCondition(HttpServletRequest request):

RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);

会判断是否支持请求方法。

	HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
    ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);

都会判断是否是CORS pre-flight。如果是则进行处理。

RequestMethodsRequestCondition.getMatchingCondition(HttpServletRequest request)

public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) {
	if (CorsUtils.isPreFlightRequest(request)) {
		return matchPreFlight(request);
	}

	if (getMethods().isEmpty()) {
		if (RequestMethod.OPTIONS.name().equals(request.getMethod()) &&
				!DispatcherType.ERROR.equals(request.getDispatcherType())) {

			return null; // We handle OPTIONS transparently, so don't match if no explicit declarations
		}
		return this;
	}

	return matchRequestMethod(request.getMethod());
}

CorsUtils.isPreFlightRequest判断是否是OPTION预检请求,如果调用matchPreFlight从请求头Access-Control-Request-Method获取请求方法,进行判断是否支持请求方法。如果不是OPTION预检请求且是OPTION请求方法返回null。否则调用matchRequestMethod匹配请求方法。

CorsUtils.isPreFlightRequest(HttpServletRequest request)

public static boolean isPreFlightRequest(HttpServletRequest request) {
	return (HttpMethod.OPTIONS.matches(request.getMethod()) &&
			request.getHeader(HttpHeaders.ORIGIN) != null &&
			request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null);
}

请求方法是OPTIONS且Origin和Access-Control-Request-Method请求头不为null才是CORS pre-flight。

AbstractHandlerMapping.getHandler(HttpServletRequest request)方法中getHandlerExecutionChain(handler, request)获取到拦截器链之后:

if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
		CorsConfiguration config = getCorsConfiguration(handler, request);
		if (getCorsConfigurationSource() != null) {
			CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
			config = (globalConfig != null ? globalConfig.combine(config) : config);
		}
		if (config != null) {
			config.validateAllowCredentials();
		}
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}

hasCorsConfigurationSource判断HandleMapping是否有CROS配置;CorsUtils.isPreFlightRequest判断是否是CORS pre-flight。如果HandleMapping是否有CROS配置或者是CORS pre-flight则获取CROS配置并设置cors拦截器。如果corsConfigurationSource不为null,从corsConfigurationSource获取cros配置并且和已有的CorsConfiguration合并。springmvc配置文件中的mvc:cors配置加载到corsConfigurationSource中。

AbstractHandlerMethodMapping.getCorsConfiguration(Object handler, HttpServletRequest request)

protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {
	CorsConfiguration corsConfig = super.getCorsConfiguration(handler, request);
	if (handler instanceof HandlerMethod) {
		HandlerMethod handlerMethod = (HandlerMethod) handler;
		if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {
			return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG;
		}
		else {
			CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod);
			corsConfig = (corsConfig != null ? corsConfig.combine(corsConfigFromMethod) : corsConfigFromMethod);
		}
	}
	return corsConfig;
}

1、从父类获取CROS配置
2、如果handle是HandlerMethod,且handlerMethod不等于PREFLIGHT_AMBIGUOUS_MATCH,则从mappingRegistry获取cros配置且和父类获取的cros配置合并。

AbstractHandlerMapping.getCorsConfiguration(Object handler, HttpServletRequest request)

protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {
	Object resolvedHandler = handler;
	if (handler instanceof HandlerExecutionChain) {
		resolvedHandler = ((HandlerExecutionChain) handler).getHandler();
	}
	if (resolvedHandler instanceof CorsConfigurationSource) {
		return ((CorsConfigurationSource) resolvedHandler).getCorsConfiguration(request);
	}
	return null;
}

从handle中获取CROS配置。获取不到返回null。

AbstractHandlerMapping.getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, @Nullable CorsConfiguration config)

	protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
		HandlerExecutionChain chain, @Nullable CorsConfiguration config) {

	if (CorsUtils.isPreFlightRequest(request)) {
		HandlerInterceptor[] interceptors = chain.getInterceptors();
		return new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
	}
	else {
		chain.addInterceptor(0, new CorsInterceptor(config));
		return chain;
	}
}

1、如果是CORS pre-flight则设置PreFlightHandler为handle。
2、否则添加CorsInterceptor拦截器到首位。
如果是OPTION预检请求,则会调用PreFlightHandler处理请求。处理完OPTION预检请求后才是实际的请求。此时会走下面的分支。会添加CorsInterceptor。

PreFlightHandler.handleRequest(HttpServletRequest request, HttpServletResponse response)

public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
		corsProcessor.processRequest(this.config, request, response);
	}

DefaultCorsProcessor.processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,HttpServletResponse response)

public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
		HttpServletResponse response) throws IOException {

	Collection<String> varyHeaders = response.getHeaders(HttpHeaders.VARY);
	if (!varyHeaders.contains(HttpHeaders.ORIGIN)) {
		response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
	}
	if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD)) {
		response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
	}
	if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS)) {
		response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
	}

	if (!CorsUtils.isCorsRequest(request)) {
		return true;
	}

	if (response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) != null) {
		logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");
		return true;
	}

	boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
	if (config == null) {
		if (preFlightRequest) {
			rejectRequest(new ServletServerHttpResponse(response));
			return false;
		}
		else {
			return true;
		}
	}

	return handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest);
}

1、获取Vary请求头并判断Vary请求头是否包含Origin,Access-Control-Request-Method,Access-Control-Request-Headers请求头,如果包含则设置相应的请求头到response中。
2、CorsUtils.isCorsRequest判断是否存在跨域,不存在返回true。
3、如果有Access-Control-Allow-Origin请求头返回true。
4、如果cros配置为空且是OPTION预检请求rejectRequest拒绝请求,设置响应状态码为403。如果存在跨域且没有设置CROS则会拒绝请求。
5、否则调用handleInternal处理请求

CorsUtils.isCorsRequest(HttpServletRequest request)

	public static boolean isCorsRequest(HttpServletRequest request) {
	String origin = request.getHeader(HttpHeaders.ORIGIN);
	if (origin == null) {
		return false;
	}
	UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build();
	String scheme = request.getScheme();
	String host = request.getServerName();
	int port = request.getServerPort();
	return !(ObjectUtils.nullSafeEquals(scheme, originUrl.getScheme()) &&
			ObjectUtils.nullSafeEquals(host, originUrl.getHost()) &&
			getPort(scheme, port) == getPort(originUrl.getScheme(), originUrl.getPort()));

}

通过请求协议,域名和端口判断是否存在跨域。返回false表示不存在跨域。true表示存在跨域。

DefaultCorsProcessor.handleInternal(ServerHttpRequest request, ServerHttpResponse response,CorsConfiguration config, boolean preFlightRequest)

protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
		CorsConfiguration config, boolean preFlightRequest) throws IOException {

	String requestOrigin = request.getHeaders().getOrigin();
	String allowOrigin = checkOrigin(config, requestOrigin);
	HttpHeaders responseHeaders = response.getHeaders();

	if (allowOrigin == null) {
		logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
		rejectRequest(response);
		return false;
	}

	HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
	List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
	if (allowMethods == null) {
		logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
		rejectRequest(response);
		return false;
	}

	List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
	List<String> allowHeaders = checkHeaders(config, requestHeaders);
	if (preFlightRequest && allowHeaders == null) {
		logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
		rejectRequest(response);
		return false;
	}

	responseHeaders.setAccessControlAllowOrigin(allowOrigin);

	if (preFlightRequest) {
		responseHeaders.setAccessControlAllowMethods(allowMethods);
	}

	if (preFlightRequest && !allowHeaders.isEmpty()) {
		responseHeaders.setAccessControlAllowHeaders(allowHeaders);
	}

	if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
		responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
	}

	if (Boolean.TRUE.equals(config.getAllowCredentials())) {
		responseHeaders.setAccessControlAllowCredentials(true);
	}

	if (preFlightRequest && config.getMaxAge() != null) {
		responseHeaders.setAccessControlMaxAge(config.getMaxAge());
	}

	response.flush();
	return true;
}

1、checkOrigin检查Origin请求头
2、若Origin请求头为空,表示不允许来源访问。rejectRequest拒绝请求。
3、checkMethods检查请求方法,若不支持请求方法调用rejectRequest拒绝请求。
4、checkHeaders检查请求头,如果是OPTION预检请求且不支持请求头调用rejectRequest拒绝请求。
5、否则允许该来源访问。将allowedOrigin设置到Access-Control-Allow-Origin响应头
6、设置Access-Control-Allow-Headers,Access-Control-Expose-Headers,Access-Control-Allow-Credentials,Access-Control-Max-Age等响应头。

通过上面可知OPTION预检请求判断请求是否存在跨域,是否存在cros配置,是否存在Origin请求头。如果不存在跨域返回true。
如果存在跨域,

  1. cros配置不支持Origin请求头
  2. cros配置不支持请求方法,请求头
    如果存在以上情况则会拒绝请求。

如果cros支持跨域的请求来源,则会设置Access-Control-Allow-Origin,Access-Control-Allow-Headers,Access-Control-Expose-Headers,Access-Control-Allow-Credentials,Access-Control-Max-Age等响应头。

如果设置CorsInterceptor拦截器,在DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response)

	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}

执行实际handle之前执行preHandle方法。

CorsInterceptor.preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		// Consistent with CorsFilter, ignore ASYNC dispatches
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		if (asyncManager.hasConcurrentResult()) {
			return true;
		}

		return corsProcessor.processRequest(this.config, request, response);
	}

1、如果存在异步web则返回true。
2、否则调用corsProcessor.processRequest。corsProcessor为DefaultCorsProcessor。上面以看过。如果corsProcessor.processRequest返回false,则不会执行实际handle处理请求。

标签:return,跨域,SpringMVC,request,CORS,null,config,response,请求
From: https://www.cnblogs.com/shigongp/p/16926256.html

相关文章

  • 深和jsonp【转】 jsonpk跨域问题详解
    取不到数据!上周客户新买了服务器,原本在旧的服务器上放着客户的Web主页信息和一个后台程序(asp.net),在客户的主页中有一个动态显示最新消息的处理,这个处理就是通过ajax异......
  • SpringMVC
    SpringMVC的基本流程:浏览器发送请求,如请求地址符合前端控制器的url-pattern,该请求就会被前端控制器DispatcherServlet进行处理,前端控制器会读取SpringMVC的核心配置文件,......
  • SpringMVC组件
    SpringMVC的相关组件前端控制器:DispatcherServlet处理器映射器:HandlerMapping处理器适配器:HandlerAdapter处理器:Handler视图解析器:ViewResolver......
  • CDN加速WordPress触发CORS导致跨域加载失败
    这两天折腾​​CDN加速​​​来提升自己博客的访问速度,用的阿里云​​CDN加速​​方案;使用的时候发现一个问题,部分资源CDN加速失败,原因是触发了CORS,因为CDN加速网址与博客......
  • 什么是前端跨域,怎么解决跨域问题
    什么是跨域?跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。所谓同源是指:域名,协议,端口均相同,不明白没关系,举个栗子:http://w......
  • 求超大文件上传方案( SpringMVC )
    ​ 对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线......
  • springmvc环境部署报错: NoClassDefFoundError: org/springframework/web/cors/CorsPro
    部署springmvc的时候报出一个很奇怪的错误:org.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname‘org.springframework.web.servlet.......
  • Spring —— SpringMVC简介
    SpringMVCSpringMVC技术与Servlet技术功能等同,均属于web层开发技术是一种基于java实现MVC模型的轻量级Web框架 SpringMVC入门案例           ......
  • 记录一次uniapp 开发环境访问本机接口 报跨域的问题
    api接口使用的springboot。已确认设置允许所有跨域请求(*)接口在本机启动(接口地址:http://localhost:9999)uniapp在本机启动(本地web:http://localhost:8080)报错信息:Acces......
  • 跨域请求
    同源策略ajax不能在不同源的情况下获取数据jsonp原理......