24、Spring Security 实战 - Spring Security环境中存在的跨域问题

前言

在前后端不分离时,我们利用前面讲过的Spring Security的各种知识点,就可以实现对项目的权限管控。但是在前后端分离时,尤其是在引入了Spring Security后的前后端分离时,我们从前端发来的请求,就会存在一些问题。这些问题就是跨域而导致的问题!

对于前后端分离时,跨域而产生的安全问题,我们该怎么解决呢?接下来请跟着 来学习如何解决吧!

在解决跨域问题之前,我们先来了解一下何为跨域问题,怎么产生的跨域问题,怎么解决这个跨域问题。

一. 跨域问题的由来

1. 同源策略

跨域问题的产生,源自浏览器的一个同源策略。

1.1 同源策略的概念

同源策略是由 Netscape 提出的一个著名的安全策略,它是浏览器最核心也是最基本的安全功能,所有支持 JavaScript 的浏览器都会使用这个策略**。在同源策略中,要求 域名、协议、端口 3部分都要相同**。

举例来说,http://www.yiyige.com:80/dir/syc.html 这个网址,http是协议,www.yiyige.com是域名,80是端口号(80端口号默认可以省略)。同源策略具体规则如下表:

 

如果浏览器中没有对JavaScript进行同源限制,则有可能会利用浏览器的漏洞,出现以下针对服务器的攻击方式

  • CSRF攻击
  • XSS攻击
  • 跨域问题

1.2 同源策略的作用

通过上一小节可知,同源策略的出现就是为了防止针对服务器的攻击,比如为了防止恶意网站通过冒充用户信息来窃取用户的敏感数据信息等,所以同源策略可以限制以下行为:

  • 限制读取Cookie、LocalStorage 和 IndexDB等信息;
  • 限制获取 DOM 和 JS 对象;
  • 限制发送Ajax请求。

即这个同源策略提高了攻击成本,保证了服务器的安全。

2. 跨域问题

2.1 跨域的作用

在上面的小节中,我给大家说到,浏览器出于安全方面的考虑,做了一个同源策略的限制,即不允许跨域访问其他资源,通俗的说就是浏览器中不能跨站执行其他网站的脚本,这是浏览器对JavaScript实施的一种安全限制。

2.2 跨域的概念

比如我们现在有一个地址http://store.company.com/dir/page.html,根据同源策略,如果对以下地址进行请求,产生的结果如下表所示:

 

也就是说如果域名、协议、端口号三者之间,只要有一个不同,就认为不是一个网站,也就可能会存在跨域问题

二. 常见的攻击手段

在上面的小节中,我还给各位提到了几种攻击手段,接下来我们一起来了解一下这些攻击方式,了解其攻击原理,这样以后就可以想办法进行防御。

1. CSRF攻击

1.1 CSRF攻击

CSRF(Cross-Site Request Forgery)跨站请求伪造这是一种利用带有用户登录状态的cookie进行安全操作的攻击方式攻击者会盗用你的身份,以你的名义来发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作。比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。如下图所示: 其中Web A为存在CSRF漏洞的网站,Web B为攻击者构建的恶意网站,User C为Web A网站的合法用户。

 

1.2 CSRF攻击原理及过程

1、用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;

2、在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;

3、用户未退出网站A之前,在同一浏览器中,打开另一个TAB页访问网站B;

4、网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;

5、浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

1.3 CSRF防护措施

总体来说,CSRF攻击就是利用了系统对登录器用户的信任,使得用户执行了某些并非意愿的操作,从而造成用户的损失**。所以我们对CSRF防护的一个重点是对“用户凭证”进行校验处理,通过这种机制可以对用户的请求是合法进行判断,判断是不是跨站攻击的行为**。因为“用户凭证”是Cookie中存储的,所以防护机制的处理对像也是Cookie的数据,我们要在防护的数据中加入签名校验,并对数据进行生命周期时间管理,就是数据过期管理。

2. XSS攻击

2.1 XSS攻击

XSS又叫CSS(Cross Site Script),跨站脚本攻击。它是指恶意攻击者在Web页面里插入恶意的JavaScript代码,当用户浏览该网页时,嵌入在Web里面的JavaScript代码会被执行,从而达到恶意的特殊目的。XSS属于被动式的攻击,因为其被动且不好利用,所以许多人常呼略其危害性。

2.2 XSS攻击原理

 

2.3 XSS防护措施

防护XSS攻击其实就记住两条原则:过滤输入和转义输出。具体措施如下:

  • 在输入方面对所有用户提交内容进行可靠的输入验证,提交内容包括URL、查询关键字、http头、post数据等;
  • 在输出方面,在用户输内容中使用标签,标签内的内容不会解释,直接显示;
  • 严格执行字符输入字数控制;
  • 在脚本执行区中,应绝无用户输入。

三. 跨域问题的解决方案

1. 跨域解决方案

针对上文提到的跨域问题,我们肯定要想办法进行解决,现在常见的解决方案有很多种,比如:

  • Java后端进行跨域解决CORS;
  • 使用AJAX的JSONP;
  • 使用jQuery的JSONP插件;
  • document.domain + iframe 跨域解决方案;
  • window.name + iframe 跨域解决方案;
  • location.hash + iframe 跨域解决方案;
  • postMessage跨域解决方案;
  • WebSocket协议跨域解决方案;
  • node代理跨域解决方案;
  • nginx代理跨域解决方案;
  • ......

2. JSONP与CORS对比

在这些众多的解决方案中,最常用的是JSONP和CORS,其中比较传统的跨域解决方案是 JSONP。JSONP 虽然能解决跨域问题,但是有一个很大的局限性,那就是 只支持 GET 请求,而不支持其他类型的请求,在 RESTful 时代几乎就没什么用。

另外一种常见的解决方案是 CORS(跨域源资源共享,Cross-Origin Resource Sharing),它是一个 W3C 标准,或者说是一种针对浏览器的技术规范,提供了 Web 服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,进而实现跨域访问。

3. CORS跨域解决方案

我这里主要是在SpringBoot项目中利用CORS技术,来解决跨域方案。

3.1 CORS简介

CORS是一个W3C标准,全称是**"跨域资源共享"(Cross-origin resource sharing)**,在CORS的规范中有一组新增的HTTP首部字段,允许服务器声明其提供的资源可以被哪些站点跨域使用。最终允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制

CORS需要浏览器和服务器同时支持。它的通信过程,都是浏览器自动完成的,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX/Fetch通信没有差别,代码完全一样。浏览器一旦发现请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以跨源通信

3.2 CORS请求分类

  • 简单请求(simple request): 简单请求就是表单请求,浏览器沿袭了传统的处理方式,不把行为复杂化,否则开发者可能转而使用表单,规避 CORS 的限制。
  • 非简单请求(not-so-simple request),也被称为“预检请求”。
  • 带凭证的请求,即携带了用户cookie等信息的请求。

接下来我对上面提到的简单请求和非简单请求进行详解,请继续往下看。

4. 简单请求

浏览器发出CORS简单请求,只需要 在头信息之中增加一个Origin字段

所谓的简单请求,就是指HEAD、GET、POST请求,并且HTTP的头信息是以下几种字段之一:
Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type。

注意:

Content-Type只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

简单请求原理图如下所示:

 

5. 非简单请求

简单请求之外的其余请求,就是非简单请求。浏览器发出CORS非简单请求,会在正式通信之前,增加了一次OPTIONS查询请求,称为"预检"请求(preflight),用于确认服务器是否允许跨域。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段,只有得到肯定的答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。非简单请求原理图如下:

 

6. 响应头详解

要实现CORS很简单,我们只需在服务端添加一些响应头,并且这样做对前端来说是无感知的,很方便,这些响应头如下:

  • Access-Control-Allow-Origin

该字段是必填字段。它的值要么是请求时Origin字段的具体值,要么是一个*,表示被允许的站点。

  • Access-Control-Allow-Methods

该字段是必填字段。它的值是逗号分隔的一个具体的字符串或者*,表示服务器允许跨域的请求方法。

注意:

返回的是所有支持的方法,而不单是浏览器请求的那个方法,这是为了避免多次"预检"请求。

  • Access-Control-Expose-Headers

该字段可选,仅在预检请求的响应字段中有效,表示服务器允许携带的请求头。当CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

  • Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,不发送Cookie,即false。如果对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json,这个值只能设为true。如果服务器不要浏览器发送Cookie,删除该字段即可。

  • Access-Control-Max-Age

该字段可选,用来指定本次预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求。

注意:

如果在开发中,发现每次发起的请求都是两条,一次OPTIONS,一次正常请求。注意是每次,那么就需要配置Access-Control-Max-Age,避免每次都发出预检请求。

至此,我就给各位讲解了关于跨域问题的一些理论知识,接下来会在下一章节中,给大家讲解如何进行代码实现,敬请期待!