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

一次正常的请求

最近别人需要调用我们系统的某一个功能,对方希望提供一个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请求。假如当前域名是www.abc.com,那么在当前环境中运行的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实现处理跨域请求代码详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题。如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

(0)

相关推荐

  • 详解SpringBoot多跨域请求的支持(JSONP)

    在我们做项目的过程中,有可能会遇到跨域请求,所以需要我们自己组装支持跨域请求的JSONP数据,而在4.1版本以后的SpringMVC中,为我们提供了一个AbstractJsonpResponseBodyAdvice的类用来支持jsonp的数据(SpringBoot接收解析web请求是依赖于SpringMVC实现的).下面我们就看一下怎么用AbstractJsonpResponseBodyAdvice来支持跨域请求. 使用AbstractJsonpResponseBodyAdvice来支持跨域请求

  • Spring Boot Web应用开发 CORS 跨域请求支持

    一.Web开发经常会遇到跨域问题,解决方案有:jsonp,iframe,CORS等等 CORS与JSONP相比 1. JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求. 2. 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理. 3. JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS 浏览器支持情况 Chrome 3+ Firefox 3.5+ Opera 12+ Sa

  • Springmvc ajax跨域请求处理方法实例详解

    上次给一个网站写网站  前后端分离 最后跪在ajax跨域上面了  自己在网上找了个方法  亲试可用  记录一下 写一个类  继承HandlerInterceptorAdapter package com.util; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.handler.Ha

  • spring boot配合前端实现跨域请求访问

    一.方法: 服务端设置Respone Header头中Access-Control-Allow-Origin 配合前台使用jsonp 继承WebMvcConfigurerAdapter 添加配置类 二.实例: 1.前端:因为我们用了前后端分离,前端用node服务器,node服务器再用了ajax反向代理请求到我的spring boot 服务器.其中node服务器也用了ajax发出请求所以也存在跨域的问题.具体代码: app.all(apiRoot + '/*', proxy('127.0.0.1:

  • 详解java 中Spring jsonp 跨域请求的实例

    详解java 中Spring jsonp 跨域请求的实例 jsonp介绍 JSONP(JSON with Padding)是JSON的一种"使用模式",可用于解决主流浏览器的跨域数据访问的问题.由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外.利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSO

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

    一次正常的请求 最近别人需要调用我们系统的某一个功能,对方希望提供一个api让其能够更新数据.由于该同学是客户端开发,于是有了类似以下代码. @RequestMapping(method = RequestMethod.POST, value = "/update.json", produces = MediaType.APPLICATION_JSON_VALUE) public @ResponseBody Contacter update(@RequestBody Contacter

  • JS JSOP跨域请求实例详解

    在项目开发中遇到跨域的问题,一般都是通过JSONP来解决的.但是JSONP到底是个什么东西呢,实现的原理又是什么呢.在项目的空闲时间可以好好的来研究一下了. 1.什么是JSONP? 要了解JSONP,不得不提一下JSON,那么什么是JSON? JSON is a subset of the object literal notation of JavaScript. Since JSON is a subset of JavaScript, it can be used in the langu

  • JSONP跨域请求实例详解

    JSOP简介 JSONP(JSON with Padding)是JSON的一种"使用模式",可用于解决主流浏览器的跨域数据访问的问题.由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外.利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP.用 JSON

  • Vue Ajax跨域请求实例详解

    一.设置config/index.js || proxyTable添加 proxyTable: { '/api': { target: 'http://192.168.48.239:8080/ydzl', changeOrigin: true, pathRewrite: { '^/api': '' } } 二.mian.js 添加vue全局属性 Vue.prototype.HOST = '/api' 三.如果是post的话 1.修改数据格式 transformRequest: [function

  • Nginx配置跨域请求Access-Control-Allow-Origin * 详解

    前言 当出现403跨域错误的时候 No 'Access-Control-Allow-Origin' header is present on the requested resource,需要给Nginx服务器配置响应的header参数: 一. 解决方案 只需要在Nginx的配置文件中配置以下参数: location / { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET

  • Spring Boot设置支持跨域请求过程详解

    现代浏览器出于安全的考虑, HTTP 请求时必须遵守同源策略,否则就是跨域的 HTTP 请求,默认情况下是被禁止的,IP(域名)不同.或者端口不同.协议不同(比如 HTTP.HTTPS)都会造成跨域问题. 一般前端的解决方案有: ① 使用 JSONP 来支持跨域的请求,JSONP 实现跨域请求的原理简单的说,就是动态创建<script>标签,然后利用<script>的 SRC 不受同源策略约束来跨域获取数据.缺点是需要后端配合输出特定的返回信息. ② 利用反应代理的机制来解决跨域的

  • Django实现跨域请求过程详解

    前言 CORS 即 Cross Origin Resource Sharing 跨域资源共享. 跨域请求分两种:简单请求.复杂请求. 简单请求 简单请求必须满足下述条件. HTTP方法为这三种方法之一:HEAD.GET.POST HTTP头消息不超出以下字段: Accept.Accept-Language.Content-Language.Last-Event-ID 且Content-Type只能为下列类型中的某一个: application/x-www-from-urlencoded mult

  • django基于cors解决跨域请求问题详解

    一 同源策略 同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响.可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现 请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同. 比如:我在本地上的域名是127.0.0.1:8000,请求另外一个域名:127.0.0.1:8001一段数据 浏览器上就会报错,这个就是同源策略的保护,如果浏览器对javas

  • Spring boot跨域设置实例详解

    定义:跨域是指从一个域名的网页去请求另一个域名的资源 1.原由 公司内部有多个不同的子域,比如一个是location.company.com ,而应用是放在app.company.com , 这时想从 app.company.com去访问 location.company.com 的资源就属于跨域 本人是springboot菜鸟,但是做测试框架后端需要使用Springboot和前端对接,出现跨域问题,需要设置后端Response的Header.走了不少坑,在这总结一下以备以后使用 2.使用场景

  • 关于python 跨域处理方式详解

    因为浏览器的同源策略限制,不是同源的脚本不能操作其他源下面的资源,想操作另一个源下面的资源就属于跨域了,这里说的跨域是广义跨域,我们常说的代码中请求跨域,是狭义的跨域,即在脚本代码中向非同源域发送http请求 浏览器的同源策略(SOP/same origin policy)是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS(跨站脚本攻击 cross site scripting)和CSRF(跨站请求伪造cross-site request forgery)等攻击. (同

随机推荐