文档章节

Spring @CrossOrigin 注解原理

暗中观察
 暗中观察
发布于 03/13 23:27
字数 902
阅读 661
收藏 9

现实开发中,我们难免遇到跨域问题,以前笔者只知道jsonp这种解决方式,后面听说spring只要加入@CrossOrigin即可解决跨域问题。本着好奇的心里,笔者看了下@CrossOrigin 作用原理,写下这篇博客。

先说原理:其实很简单,就是利用spring的拦截器实现往response里添加 Access-Control-Allow-Origin等响应头信息,我们可以看下spring是怎么做的

注:这里使用的spring版本为5.0.6

我们可以先往RequestMappingHandlerMapping 的initCorsConfiguration方法打一个断点,发现方法调用情况如下

如果controller在类上标了@CrossOrigin或在方法上标了@CrossOrigin注解,则spring 在记录mapper映射时会记录对应跨域请求映射,代码如下

RequestMappingHandlerMapping
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		Class<?> beanType = handlerMethod.getBeanType();
        //获取handler上的CrossOrigin 注解
		CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);
       //获取handler 方法上的CrossOrigin 注解
		CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);
        
		if (typeAnnotation == null && methodAnnotation == null) {
            //如果类上和方法都没标CrossOrigin 注解,则返回一个null
			return null;
		}
        //构建一个CorsConfiguration 并返回
		CorsConfiguration config = new CorsConfiguration();
		updateCorsConfig(config, typeAnnotation);
		updateCorsConfig(config, methodAnnotation);

		if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
			for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
				config.addAllowedMethod(allowedMethod.name());
			}
		}
		return config.applyPermitDefaultValues();
	}

将结果返回到了AbstractHandlerMethodMapping#register,主要代码如下

	CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
//会保存handlerMethod处理跨域请求的配置
					this.corsLookup.put(handlerMethod, corsConfig);
				}

当一个跨域请求过来时,spring在获取handler时会判断这个请求是否是一个跨域请求,如果是,则会返回一个可以处理跨域的handler

AbstractHandlerMapping#getHandler	
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		//如果是一个跨域请求
if (CorsUtils.isCorsRequest(request)) {
        //拿到跨域的全局配置
			CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
          //拿到hander的跨域配置
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
           //处理跨域(即往响应头添加Access-Control-Allow-Origin信息等),并返回对应的handler对象
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

我们可以看下如何判定一个请求是一个跨域请求,

public static boolean isCorsRequest(HttpServletRequest request) {
//判定请求头是否有Origin 属性即可
		return (request.getHeader(HttpHeaders.ORIGIN) != null);
	}

再看下getCorsHandlerExecutionChain 是如何获取一个handler

	protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
			HandlerExecutionChain chain, @Nullable 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;
	}

也就是在调用目标方法前会先调用CorsInterceptor#preHandle,我们观察得到其也是调用了corsProcessor.processRequest方法,我们往这里打个断点

processRequest方法的主要逻辑如下

	public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
       //....
       //调用了自身的handleInternal方法
		return handleInternal(serverRequest, serverResponse, config, 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();

		responseHeaders.addAll(HttpHeaders.VARY, Arrays.asList(HttpHeaders.ORIGIN,
				HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));

		if (allowOrigin == null) {
			logger.debug("Rejecting CORS request because '" + 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("Rejecting CORS request because '" + requestMethod + "' request method 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("Rejecting CORS request because '" + requestHeaders + "' request headers 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;
	}

至此@CrossOrigin的使命就完成了,说白了就是用拦截器给response添加响应头信息而已

© 著作权归作者所有

暗中观察

暗中观察

粉丝 8
博文 138
码字总数 48746
作品 0
惠州
私信 提问
Spring-boot2.0 前后端分离项目 跨域问题

将Spring-boot 从1.5.x升级到2.0后,浏览器出现跨域问题: 原因:Spring-boot2.0后 allowCredentials为false 解决方式: 1.全局设置: 注意:corsconfig 实现方法也不一样,1.5.x WebMvcConf...

驛路梨花醉美
2018/08/20
434
0
spring boot cors 允许跨域请求

spring boot cors 实现 官方文档:https://spring.io/blog/2015/06/08/cors-support-in-spring-framework 有三种方法,可以通过在程序中通过代码,允许跨域请求。 1 声明一个CORS过滤器 统一...

pding
2018/05/22
260
0
Spring 跨域支持(CROS)注解:@CrossOrigin

Spring MVC 4.2 增加 CORS 支持 跨站 HTTP 请求(Cross-site HTTP request)是指发起请求的资源所在域不同于该请求所指向资源所在的域的 HTTP 请求。比如说,域名A(http://domaina.example)的某...

孟飞阳
2017/11/08
309
0
SpringMVC跨域接收JSON

编者注 由于要编写一个统一的xmlrpc调用与rest,里面涉及到了跨域调用问题。随即有以下内容 Spring的跨域 Spring 4.2.x 编者原本是想在Spring响应内容添加跨域请求,但由于不好划定跨域范围,...

抢小孩糖吃
2016/07/11
454
0
Java服务端Cors跨域资源共享配置,解决与Spring Security冲突引起的问题

(一) CORS介绍 CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。 re...

hhjian
2017/11/01
383
0

没有更多内容

加载失败,请刷新页面

加载更多

实战项目-学成在线(一)

之前看的黑马程序员实战项目之一,打算以博客的形式写出来,也让自己重新温习一下。 1、项目背景 略(就是当前这东西很火,我们重点在开发,这些就略过) 2、功能模块 门户,学习中心,教学管...

lianbang_W
24分钟前
2
0
基于Vue的数字输入框组件开发

本文转载于:专业的前端网站➫基于Vue的数字输入框组件开发 1、概述 Vue组件开发的API:props、events和slots 2、组件代码 github地址:https://github.com/MengFangui/VueInputNumber 效果:...

前端老手
32分钟前
2
0
百度地图根据经纬度获取运动轨迹

<!DOCTYPE html><html><head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="initial-scale=1.0, user-scalable=n......

泉天下
34分钟前
4
0
学习记录(day04-axios增删改查、v-for循环、页面加载成功处理函数)

[TOC] 1.1 基本语法:插值表达式 <template> <div> {{username}} <br/> {{1+2+3}} <br/> {{'你的名字是:' + username}} <br/> {{'abc'.split('')}} </div><......

庭前云落
今天
3
0
CentOS Linux 7上将ISO映像文件写成可启动U盘

如今,电脑基本上都支持U盘启动,所以,可以将ISO文件写到U盘上,用来启动并安装操作系统。 我想将一个CentOS Linux 7的ISO映像文件写到U盘上,在CentOS Linux 7操作系统上,执行如下命令: ...

大别阿郎
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部