09.SpringBoot 之跨域问题

SpringBoot 之跨域问题

部分参考自:https://www.javaboy.org/2019/0412/springboot-cors.html

同源策略

很多人对跨域有一种误解,以为这是前端的事,和后端没有关系。事实上不是这样的,说到跨域,就不得不说说浏览器的同源策略。同源策略是有NetScape提出的一个著名的安全策略,它是浏览器最核心也是最基本的安全能,现在所有支持JavaScript的浏览器都会使用这个策略。所谓同源是指 协议、域名以及端口要相同。同源策略是基于安全方面的考虑提出来的,这个策略本身是没有问题,但是我们在实际开发中,由于各种原因又经常有跨域的需求,传统的跨域方案是JSONP,JSONP虽然能解决跨域但是有一个很大的局限性,那就是只支持GET请求,不支持其它类型的请求,而今天我们说的CORS(跨域源资源共享)(CORS Cross-origin Resource Sharing) 是一个 W3C标准,它是一份浏览器技术的规范,提供了Web服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,这是JSONP模板的现代版。

在Spring框架中,对于CORS也提供了相应的解决方案,今天我们就来看看SpringBoot中如何实现CORS。

实践

首先我们创建两个项目,一个项目(springboot-cors1)的端口就设置为默认的8080,另一个(springboot-cors2)端口设置为8081.

在springboot-cors1中定义如下内容:

1
2
3
4
5
6
7
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello cors!";
}
}

启动项目。我们接下来把请求页面写在项目springboot-cors2中,(导入jQuery):

classpath:/resources/static/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="jquery-3.4.1.min.js"></script>
</head>
<body>
<div id="app"></div>
<input type="button" value="get" onclick="getData()">
<script>
function getData() {
$.get('http://localhost:8080/hello',function (msg) {
$("#app").html(msg);
});
}
</script>
</body>
</html>

启动项目,请求:

我们F12签到springboot-cors2的前端请求页面抛出错误了。No 'Access-Control-Allow-Origin Header is present on the requested resource'. 因为同源问题,请求无法发送成功。

我们在SpringBoot使用CORS可以在不修改前端的任何代码时,实现跨域,那么接下来我们看看在springboot-cors1中需要如何进行配置。

方式一:

1
2
3
4
5
6
7
8
@RestController
public class HelloController {
@GetMapping("/hello")
@CrossOrigin(origins = "http://localhost:8081")//跨域
public String hello(){
return "hello cors!";
}
}

我们可以使用注解@CrossOrigin 注解并指定源来实现跨域,但是这种情况下,如果一个Controller中有很多方法,肯定不可能手动在每一个方法上加注解。所以我们可以将注解加到类上:

1
2
3
4
5
6
7
8
9
@RestController
@CrossOrigin(origins = "http://localhost:8081")//跨域 可以加载类上,也可以加上方法上
public class HelloController {
@GetMapping("/hello")
// @CrossOrigin(origins = "http://localhost:8081")//跨域
public String hello(){
return "hello cors!";
}
}

此时我们测试,可以发现,可以正常访问了。

但是我们在项目中,肯定不可能将所有的接口都放在一个Controller中,有没有一种方法可以全局配置呢?当然。我们可以通过下面这种方法:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8081")
.allowedHeaders("*")
.allowedMethods("*")//支持所有的方法
.maxAge(30 * 1000)//有效时间
;
}
}

我们可以删除在Controller中使用注解的方式。而是自定义一个配置类并实现WebMvcConfigurer接口,同时重写addCorsMappings方法。如上,我们就可以全局进行配置了。此时我们再次访问,成功。如下:

存在的问题

了解了整个CORS的工作过程之后,我们通过Ajax发送跨域请求,虽然用户体验提高了,但是也有潜在的威胁存在,常见的就是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攻击。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×