SpringMVC支持跨域访问

前言

ajax的跨域访问, Spring MVC提供了使用AOP的解决方案。

什么是跨域访问

比如我现在在 http://a.com 中, 要访问 http://b.com, 这就是跨域访问。
而一般的 AJAX 是不允许跨域访问的。
网上关于跨域的说明有很多, 简单的说, 只要 com 之前的内容不一致, 就叫跨域访问。

JSONP的解决方案(只支持GET)

Spring MVC解决方案

注意Spring MVC的版本要4.1以上
先配置 spring.xml

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans >
<!--扫描aop相关的bean -->
<context:component-scan base-package="com.nine.rivers.galaxy" use-default-filters="false">
<!-- 只扫描aop -->
<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
</beans>

添加一个 Advice 和一个 Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@ControllerAdvice(basePackages = "com.ahao.module.controller")
public class MyAdvice extends AbstractJsonpResponseBodyAdvice {
public MyAdvice () {
// 和下面 ajax 的 jsonp 属性的值要一致
super("callback");
}
}

// com.ahao.module.controller.TestController
// 注意在 MyAdvice 扫描的包下
@RestController
public class TestController {
// 只支持GET, 使用 fastjson 方便输出
@RequestMapping(value = "/test", method = RequestMethod.GET)
public JSONObject test(@RequestParam("size") Integer size) {
JSONObject json = new JSONObject(size);
for (int i = 0; i < size; i++) {
json.put("key"+i, "value"+i);
}
return json;
}
}

可以看到 Controller 是一个很普通的 Controller, 那么主要的就是 AbstractJsonpResponseBodyAdvice
从名字看就知道是和 AOP 有关。
值的注意的是, 如果把 basePackages 设置为 com 的话, 也就是很大范围的话,
那么该范围内所有的 Controller 都将支持 JSONP, 这样违背了最小权限原则
所以 basePackages 范围要尽可能小。
配置好后, 启动Tomcat, 新建一个静态页面使用 AJAX 请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div></div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
$.ajax({
type:"get",
dataType:"jsonp",
url:"http://localhost:8080/test",
jsonp:"callback", // 和 MyAdvice 配置的值要一致
data: {size: '5'},
success : function(json) {
$('div').html("成功:"+JSON.stringify(json));
},
error: function(xhr){
$('div').html("失败:"+JSON.stringify(xhr));
}
});
</script>

结果

1
<div>成功:{"key4":"value4","key3":"value3","key0":"value0","key2":"value2","key1":"value1"}</div>

为什么不支持POST

没有支持postscript标签!!!
JSONP的本质, 是通过script标签, 以函数传参的形式来调用。
从响应头Content-Type: application/javascript;charset=utf-8就可以看出。
如本例中的JSONP请求, 等价于

1
<script src="http://localhost:8080/szlh/sdlyzwgk/test?callback=jQuery33108767440327735769_1521685929769&size=5&_=1521685929770"></script>

后台处理后, 将数据封装成一个JSON对象,返回一串函数

1
jQuery33108767440327735769_1521685929769({"key4":"value4","key3":"value3","key0":"value0","key2":"value2","key1":"value1"});

可以看到jQuery33108767440327735769_1521685929769是函数名, 后台返回的数据封装成了JSON对象参数, 然后浏览器会调用这个函数, 走到success这个方法里面。

CORS的解决方案

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource)

1
2
3
4
5
6
7
8
// 允许http://localhost:8081对本机发起CORS访问
Access-Control-Allow-Origin: http://localhost:8081
// 3628800秒内,不需要再发送预检验请求,可以缓存该结果
Access-Control-Max-Age: 3628800
// 允许各种方式的请求
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
// 允许包含的请求头
Access-Control-Allow-Headers: content-type

原生解决方案

使用过滤器Filter解决, 注册这个过滤器即可。

1
2
3
4
5
6
7
8
9
10
11
12
// com.ahao.core.filter.CORSFilter
public class CORSFilter implements Filter{
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
filterChain.doFilter(servletRequest, servletResponse);
}
}

SpringMVC解决方案

注意Spring MVC的版本要4.2以上
使用CrossOrigin注解, 指定允许进行跨域的远程地址http://localhost:8081
如果不指定, 则默认允许所有外来地址跨域访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// com.ahao.module.controller.TestController
@RestController
public class TestController {
// 支持GET、POST, 使用 fastjson 方便输出
@CrossOrigin(origins = "http://localhost:8081")
@RequestMapping(value = "/test", method = RequestMethod.POST)
public JSONObject test(@RequestParam("size") Integer size) {
JSONObject json = new JSONObject(size);
for (int i = 0; i < size; i++) {
json.put("key"+i, "value"+i);
}
return json;
}
}

CrossOrigin注解可以加在类上, 也可以加在方法上。
如果需要全局都注解的话, 需要在spring配置文件上添加

1
2
3
<mvc:cors>
<mvc:mapping path="/**" allowed-origins="http://localhost:8081"/>
</mvc:cors>

参考资料