SpringMVC支持跨域的几种姿势

跨域好像是一个前端的问题,通常是a域名下向b域名的服务发起请求,然后处于浏览器的安全原则,被拦截了,而这种场景,在实际的项目中并不少见,那么作为后端可以怎么去支持跨域的case呢?

后端需要支持跨域,一个是支持jsonp请求;还有一个就是设置responseHeader中crossOrigin等相关参数

I. Jsonp的支持

jsonp的请求表现方式就是url里面会多一个参数 callback,一般如下

1
callback=jQuery21105810685605043302_1516257942328

jsonp的返回与一般调用方式的返回也会有点区别,会在外面包装一层,如

1
jQuery21105810685605043302_1516257942328(...);

springmvc中,jsonp的支持却是比较简单了,不需要对现有的接口进行任何处理,只需要像下面这么玩即可

1
2
3
4
5
6
@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
super("callback");
}
}

分析说明

首先是利用了注解 @ControllerAdvice , 这个注解在后面说到的统一异常处理时,也会用到,从命名也可以看出,就是为Controller添加一个切面,简单来讲,就是在直接返回数据前,对返回的结果包装一把;从实现也可以看出,主要的逻辑就在 AbstractJsonpResponseBodyAdvice 里面,所以有必要看一下这个东西是怎么支持的了

核心的代码逻辑就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {

HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();

for (String name : this.jsonpQueryParamNames) {
String value = servletRequest.getParameter(name);
if (value != null) {
if (!isValidJsonpQueryParam(value)) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring invalid jsonp parameter value: " + value);
}
continue;
}

// 下面三行是主要的逻辑
MediaType contentTypeToUse = getContentType(contentType, request, response);
response.getHeaders().setContentType(contentTypeToUse);
bodyContainer.setJsonpFunction(value);
break;
}
}
}

直接看可能看不太明白究竟做了什么,写了个测试,debug下相关的参数如下

IMAGE

即,修改返回的 content-type 为: application/javascript

返回的Container里面设置了jsonpFunction,为请求参数的value,至于是在什么时候封装的返回结果呢?这个有待后续补全

II. 支持cors跨域

Cross-Origin Resource Sharing(CORS)跨来源资源共享是一份浏览器技术的规范,提供了 Web 服务从不同域传来沙盒脚本的方法,以避开浏览器的同源策略,是 JSONP 模式的现代版。与 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求

1. 背景

CORS背后的基本思想是使用自定义的HTTP头部允许浏览器和服务器相互了解对方,从而决定请求或响应成功与否

所以问题就来了,安全如何保证?

一般而言,为了避免夸站点攻击(csrf),常见的手段无非:

  • 身份校验(比如要求用户登录)
  • token验证
  • ip白名单
  • 来源referer校验
  • 频率限制

2. 实现方式

要支持csrf,也比较简单了,无非就是设置下responseHeader了, 一般需要设置以下几项

  • Access-Control-Allow-Origin: *; // 允许的来源
  • Access-Control-Allow-Methods: GET, POST, PUT, DELETE
  • Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Headers: Content-Type
  • Access-Control-Max-Age: 1800 //30 min

所以实现起来的方式就比较多了,一个是新增一个filter,主动设置下返回头,当然spring mvc提供了更友好的方式了

常见的几种手段如下:

a. xml配置方式

1
2
3
4
5
<mvc:cors>
<mvc:mapping path="/ajax/*"
allowed-origins="*"
max-age="3600" />
</mvc:cors>

b. 注解方式

在controller方法上,添加下面这个注解即可

1
2
3
4
5
@CrossOrigin(origins = "*")
@RequestMapping(value = {"xx"},
method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseWrapper<WxBaseResponse> create(HttpServletRequest httpServletRequest) {
}

c. 直接修改返回的responseHeader

1
2
3
response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");

III. 小结

上面介绍了两种方式,支持起来都比较简单

  • jsonp: 通过ControllerAdvice拦截Controller,然后继承AbstractJsonpResponseBodyAdvice即可
  • cors: 通过xml配置或者直接使用 @CrossOrigin注解

IV. 其他

声明

尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见解不全,如有问题,欢迎批评指正

扫描关注,java分享

QrCode