学习在 Spring Boot 中通过 CORS 解决跨域问题。
1 介绍
先来了解下同源策略,它是由 Netscape 提出的一个著名的安全策略,是浏览器最核心,也最基本的安全功能,现在所有支持 JavaScript 的浏览器都会使用这个策略,同源是指协议、域名以及端口要相同。传统的跨域解决方案是 JSONP , JSONP 虽然能解决跨域但是有一个很大的局限性,那就是只支持 GET 请求,不支持其他类型的请求。而 CORS ( Cross-origin resource sharing 跨域源资源共享)是一个 W3C 标准,它是一份浏览器技术的规范,提供了 Web 服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略。
2 实战
新建 Spring Boot 工程 spring-boot-corsprovider ,用来提供服务,默认 8080 端口。新增测试类 HelloController ,如下:
@RestController
public class HelloController {
@GetMapping("/get")
public String get() {
return "get";
}
@PutMapping("/put")
public String put() {
return "put";
}
}
新建 Spring Boot 工程 spring-boot-corsconsumer ,用来消费服务,配置 8081 端口。在 resources/static 下新建测试页面 index.html ,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="jquery3.3.1.js"></script>
</head>
<body>
<div id="app"></div>
<input type="button" value="GET" onclick="getData()">
<input type="button" value="PUT" onclick="putData()">
<script>
function getData() {
$.get('http://127.0.0.1:8080/get', function (msg) {
$("#app").html(msg);
});
}
function putData() {
$.ajax({
type: 'put',
url: 'http://127.0.0.1:8080/put',
success: function (msg) {
$("#app").html(msg);
}
})
}
</script>
</body>
</html>
启动项目,访问 http://120.0.0.1:8081/index.html ,点击按钮后观察浏览器控制台,报跨域错误,如下:
Access to XMLHttpRequest at 'http://127.0.0.1:8080/get' from origin 'http://127.0.0.1:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
接着修改 HelloController ,在类或方法上增加 CORS 的配置,如下:
@RestController
// @CrossOrigin(origins = "http://127.0.0.1:8081")
public class HelloController {
@GetMapping("/get")
@CrossOrigin(origins = "http://127.0.0.1:8081")
public String get() {
return "get";
}
@PutMapping("/put")
@CrossOrigin(origins = "http://127.0.0.1:8081")
public String put() {
return "put";
}
}
重启 spring-boot-corsprovider 再次测试,可以正常获取到数据。观察对应请求,发现 Response Headers 中多了 Access-Control-Allow-Origin: http://127.0.0.1:8081
表示服务端愿意接收来自 http://127.0.0.1:8081 的请求,这样浏览器就不会再去限制这个请求了。
上述CORS 的配置实在类或方法上,此外在 Spring Boot 中也支持全局配置,增加 MyWebMvcConfigurer
配置类,重写 addCorsMappings 方法,如下:
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://127.0.0.1:8081")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(30 * 1000);
}
}
3 风险
跨域问题虽然解决了,但带来了潜在的威胁,如:CSRF(Cross-site request forgery)跨站请求伪造。跨站请求伪造也被称为 one-click attack 或者 session riding ,通常缩写为 CSRF 或者 XSRF ,是一种挟制用户在当前已登录的 Web 应用程序上执行非本意操作的攻击方法,如:
假如一家银行用以运行转账操作的URL地址如下:
http://icbc.com/aa?bb=cc
,那么,一个恶意攻击者可以在另一个网站上放置如下代码:<img src="http://icbc.com/aa?bb=cc">
,如果用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会遭受损失。
基于此,浏览器在实际操作中,会对请求进行分类,分为简单请求,预先请求,带凭证的请求等,预先请求会首先发送一个 options 探测请求,和浏览器进行协商是否接受请求。默认情况下跨域请求是不需要凭证的,但是服务端可以配置要求客户端提供凭证,这样就可以有效避免 csrf 攻击。