同源策略

很多人对跨域有一种误解,以为这是前端的事,和后端没关系,其实不是这样的,说到跨域,就不得不说说浏览器的同源策略。

同源策略是由Netscape提出来的一个著名的安全策略, 它是浏览器中最核心也是最基本的安全功能, 现在所有支持JavaScript的浏览器都会使用这个策略. 所谓的同源是指协议、域名以及端口都必须要一致相同.

同源策略是基于安全方面的考虑提出来的, 这个策略本身是没有问题, 但是我们在实际的开发中, 由于各种原因又经常有跨域的需求, 传统的跨域方案是JSONP, JSONP虽然能够解决跨域, 但是也有一个很大的局限性, 那便是只支持GET请求, 不支持其他类型的请求,
而今天要说的CORS(跨域源资源共享 CORS, Cross-origin resource sharing)是一个W3C标准, 它是一份浏览器技术的规范, 提供了Web服务从不同网域传过来沙盒脚本的方法, 以避开浏览器的同源策略, 这是JSONP模式的现代版.

在Spring框架中, 对于CORS也提供了相应的解决方案.

配置

使用CORS可以在前端代码不做任何改变的情况下, 实现跨域
方法一:
可以通过@CrossOrigin注解配置某一个方法接受某一个域的请求, 该注解可以用在类或者方法上

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

该注解表示这个接口接受来自http://localhost:8081"地址的请求, 配置完成后, 重启provider, 再次发送请求, 浏览器控制台便不会报错, consumer也可以拿到数据.
观察浏览器控制台请求网络中, 可以看见请求头里多了Access-Control-Allow-Origin: http://localhost:8081
方法二:
在provider上, 如果每一个方法或者类上都去添加注解未免太过麻烦, 在SpringBoot中, 还可以通过全局配置一次性解决这个问题, 全局配置只需要在配置类中重写addCorsMappings方法即可, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 跨越请求
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径, 表示本应用的所有方法都会去处理跨域请求
registry.addMapping("/**")
// 设置允许跨域请求的域名
// 当**Credentials为true时,**Origin不能为星号,需为具体的ip地址【如果接口不带cookie,ip无需设成具体ip】
.allowedOriginPatterns("*")
// 是否允许证书 不再默认开启
.allowCredentials(true)
// 设置允许的方法, 允许通过的请求数
.allowedMethods("*")
// 设置允许的请求头
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}
}

经过这样的配置后, 便不用在每一个方法或者类上单独配置跨域了.

存在的问题

了解完整个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攻击.