欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring实现处理跨域请求代码详解

程序员文章站 2024-02-23 15:53:10
一次正常的请求 最近别人需要调用我们系统的某一个功能,对方希望提供一个api让其能够更新数据。由于该同学是客户端开发,于是有了类似以下代码。 @reques...

一次正常的请求

最近别人需要调用我们系统的某一个功能,对方希望提供一个api让其能够更新数据。由于该同学是客户端开发,于是有了类似以下代码。

@requestmapping(method = requestmethod.post, value = "/update.json", produces = mediatype.application_json_value)
public @responsebody contacter update(@requestbody contacter contacterro) {
	logger.debug("get update request {}", contacterro.tostring());
	if (contacterro.getuserid() == 123) {
		contacterro.setusername("adminupdate-wangdachui");
	}
	return contacterro;
}

客户端通过代码发起http请求来调用。接着,该同学又提出:希望通过浏览器使用js调用,于是便有跨域问题。

为何跨域

简单的说即为浏览器限制访问a站点下的js代码对b站点下的url进行ajax请求。假如当前域名是,那么在当前环境中运行的js代码,出于安全考虑,正常情况下不能访问www.zzz.com域名下的资源。

例如:以下代码再本域名下可以通过js代码正常调用接口

(function() {
	var url = "http://localhost:8080/api/home/update.json";
	var data = {
	    "userid": 123,
	    "username": "wangdachui"
	  };
	$.ajax({
		url: url,
		    type: 'post',
		    datatype: 'json',
		    data: $.tojson(data),
		    contenttype: 'application/json'
	}
	).done(function(result) {
		console.log("success");
		console.log(result);
	}
	).fail(function() {
		console.log("error");
	}
	)
}
)()

输出为:

object {userid: 123, username: "adminupdate-wangdachui"}

但是在其他域名下访问则出错:

options http://localhost:8080/api/home/update.json
xmlhttprequest cannot load http://localhost:8080/api/home/update.json. response to preflight request doesn't pass access control check: no 'access-control-allow-origin' header is present on the requested resource. origin 'null' is therefore not allowed access. the response had http status code 403.

解决方案

jsonp

使用jsonp来进行跨域是一种比较常见的方式,但是在接口已经写好的情况下,无论是服务端还是调用端都需要进行改造且要兼容原来的接口,工作量偏大,于是我们考虑其他方法。

cors协议

按照参考资料的说法:每一个页面需要返回一个名为‘access-control-allow-origin'的http头来允许外域的站点访问。你可以仅仅暴露有限的资源和有限的外域站点访问。在cor模式中,访问控制的职责可以放到页面开发者的手中,而不是服务器管理员。当然页面开发者需要写专门的处理代码来允许被外域访问。 我们可以理解为:如果一个请求需要允许跨域访问,则需要在http头中设置access-control-allow-origin来决定需要允许哪些站点来访问。如假设需要允许www.foo.com这个站点的请求跨域,则可以设置:access-control-allow-origin:http://www.foo.com。或者access-control-allow-origin: * 。 cors作为html5的一部分,在大部分现代浏览器中有所支持。

cors具有以下常见的header

access-control-allow-origin: http://foo.org
access-control-max-age: 3628800
access-control-allow-methods: get,put, delete
access-control-allow-headers: content-type
"access-control-allow-origin"表明它允许"http://foo.org"发起跨域请求
"access-control-max-age"表明在3628800秒内,不需要再发送预检验请求,可以缓存该结果
"access-control-allow-methods"表明它允许get、put、delete的外域请求
"access-control-allow-headers"表明它允许跨域请求包含content-type头

cors基本流程

首先发出预检验(preflight)请求,它先向资源服务器发出一个options方法、包含“origin”头的请求。该回复可以控制cor请求的方法,http头以及验证等信息。只有该请求获得允许以后,才会发起真实的外域请求。

spring mvc支持cors

response to preflight request doesn't pass access control check: no 'access-control-allow-origin' header is present on the requested resource. origin 'null' is therefore not allowed access. the response had http status code 403.

从以上这段错误信息中我们可以看到,直接原因是因为请求头中没有access-control-allow-origin这个头。于是我们直接想法便是在请求头中加上这个header。服务器能够返回403,表明服务器确实对请求进行了处理。

mvc 拦截器

首先我们配置一个拦截器来拦截请求,将请求的头信息打日志。

debug requesturl:/api/home/update.json 
debug method:options 
debug header host:localhost:8080 
debug header connection:keep-alive 
debug header cache-control:max-age=0 
debug header access-control-request-method:post 
debug header origin:null 
debug header user-agent:mozilla/5.0 (windows nt 6.1; wow64) applewebkit/537.36 (khtml, like gecko) chrome/49.0.2623.87 safari/537.36 
debug header access-control-request-headers:accept, content-type 
debug header accept:*/* 
debug header accept-encoding:gzip, deflate, sdch 
debug header accept-language:zh-cn,zh;q=0.8,en;q=0.6 

在posthandle里打印日志发现,此时response的status为403。跟踪springmvc代码发现,在org.springframework.web.servlet.dispatcherservlet.dodispatch中会根据根据request来获取handlerexecutionchain,springmvc在获取常规的处理器后会检查是否为跨域请求,如果是则替换原有的实例。

@override
public final handlerexecutionchain gethandler(httpservletrequest request) throws exception {
	object handler = gethandlerinternal(request);
	if (handler == null) {
		handler = getdefaulthandler();
	}
	if (handler == null) {
		return null;
	}
	// bean name or resolved handler?
	if (handler instanceof string) {
		string handlername = (string) handler;
		handler = getapplicationcontext().getbean(handlername);
	}
	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;
}

检查的方法也很简单,即检查请求头中是否有origin字段

public static boolean iscorsrequest(httpservletrequest request) {
	return (request.getheader(httpheaders.origin) != null);
}

请求接着会交由 httprequesthandleradapter.handle来处理,根据handle不同,处理不同的逻辑。前面根据请求头判断是一个跨域请求,获取到的handler为preflighthandler,其实现为:

@override
public void handlerequest(httpservletrequest request, httpservletresponse response) throws ioexception {
	corsprocessor.processrequest(this.config, request, response);
}

继续跟进

@override
public boolean processrequest(corsconfiguration config, httpservletrequest request, httpservletresponse response)
		throws ioexception {
	if (!corsutils.iscorsrequest(request)) {
		return true;
	}
	servletserverhttpresponse serverresponse = new servletserverhttpresponse(response);
	servletserverhttprequest serverrequest = new servletserverhttprequest(request);
	if (webutils.issameorigin(serverrequest)) {
		logger.debug("skip cors processing, request is a same-origin one");
		return true;
	}
	if (responsehascors(serverresponse)) {
		logger.debug("skip cors processing, response already contains \"access-control-allow-origin\" header");
		return true;
	}
	boolean preflightrequest = corsutils.ispreflightrequest(request);
	if (config == null) {
		if (preflightrequest) {
			rejectrequest(serverresponse);
			return false;
		} else {
			return true;
		}
	}
	return handleinternal(serverrequest, serverresponse, config, preflightrequest);
}

此方法首先会检查是否为跨域请求,如果不是则直接返回,接着检查是否同一个域下,或者response头里是否具有access-control-allow-origin字段或者request里是否具有access-control-request-method。如果满足判断条件,则拒绝这个请求。

由此我们知道,可以通过在检查之前设置response的access-control-allow-origin头来通过检查。我们在拦截器的prehandle的处理。加入如下代码:

response.setheader("access-control-allow-origin", "*");

此时浏览器中options请求返回200。但是依然报错:

request header field content-type is not allowed by access-control-allow-headers in preflight response.

我们注意到:在request的请求头里有access-control-request-headers:accept, content-type,但是这个请求头的中没有,此时浏览器没有据需发送请求。尝试在response中加入:

response.setheader("access-control-allow-headers", "origin, x-requested-with, content-type, accept");

执行成功:object {userid: 123, username: “adminupdate-wangdachui”}。

至此:我们通过分析原理使springmvc实现跨域,原有实现以及客户端代码不需要任何改动。

springmvc 4

此外,在参考资料2中,springmvc4提供了非常方便的实现跨域的方法。
在requestmapping中使用注解。 @crossorigin(origins = “http://localhost:9000”)
全局实现 .定义类继承webmvcconfigureradapter

public class corsconfigureradapter extends webmvcconfigureradapter{
	@override
		public void addcorsmappings(corsregistry registry) {
		registry.addmapping("/api/*").allowedorigins("*");
	}
}

将该类注入到容器中:

<bean class="com.tmall.wireless.angel.web.config.corsconfigureradapter"></bean>

总结

以上就是本文关于spring实现处理跨域请求代码详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题。如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!