CSRF防护功能
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack(一键攻击)”或者“Session Riding(会话控制)”,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。与传统的XSS攻击(Cross-site Scripting,跨站脚本攻击)相比,CSRF攻击更加难以防范,被认为比XSS更具危险性。CSRF攻击可以在受害者毫不知情的情况下以受害者的名义伪造请求发送给攻击页面,从而在用户未授权的情况下执行在权限保护之下的操作。
例如,一个用户Tom登录银行站点服务器准备进行转账操作,在此用户信息有效期内,Tom被诱导查看了一个黑客恶意网站,该网站就会获取到Tom登录后的浏览器与银行网站之间尚未过期的Session信息,而Tom浏览器的cookie中含有Tom银行账户的认证信息,此时黑客就可以以Tom认证后的合法用户名义来伪装访问Tom的银行账户进行非法操作。
在讨论如何抵御CSRF攻击之前,先要明确CSRF攻击的对象,也就是要保护的对象。从上面的例子可知,CSRF攻击是黑客借助受害者的cookie骗取服务器的信任,但是黑客并不能获取cookie,也看不到 cookie的具体内容。另外,对于服务器返回的结果,由于浏览器同源策略的限制,黑客无法进行解析。黑客所能做的就是伪造正常用户给服务器发送请求,以执行请求中所描述的命令,在服务器端直接改变数据的值,而非窃取服务器中的数据。因此,针对CSRF攻击要保护的对象是那些可以直接产生数据变化的服务,而对于读取数据的服务,可以不进行CSRF保护。例如银行系统中转账的请求会直接改变账户的金额,会遭到CSRF攻击,需要保护;而查询余额是对金额的读取操作,不会改变数据,CSRF攻击无法解析服务器返回的结果,可以不必保护。
在业界目前防御CSRF攻击主要有三种策略:
1、验证HTTP Referer字段;
2、在请求地址中添加Token并验证;
3、在HTTP头中自定义属性并验证。
Spring Security安全框架,提供了较为通用的使用Token验证方式进行CSRF防御,下面,针对Spring Security安全框架的CSRF防御进行介绍,并结合Spring Boot框架进行整合使用。
csrf()跨站请求伪造防御功能相关涉及到的主要方法及说明如表1所示。
表1 CSRF防御相关的主要方法及说明
方法 | 描述 |
---|---|
disable() | 关闭Security默认开启的CSRF防御功能 |
csrfTokenRepository(CsrfTokenRepositor csrfTokenRepository) | 指定要使用的CsrfTokenRepository(Token令牌持久化仓库)。默认是由LazyCsrfTokenRepository包装的HttpSessionCsrfTokenRepository |
requireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) | 指定针对什么类型的请求应用CSRF防护功能。默认设置是忽略GET、HEAD、TRACE和OPTIONS请求,而处理并防御其他所有请求 |
接下来,结合上表中的方法针对Spring Boot中的CSRF防护功能进行说明。
1.CSRF防护功能关闭
Spring Boot整合Spring Security默认开启了CSRF防御功能,并要求数据修改的请求方法(例如PATCH、POST、PUT和DELETE)都需要经过Security配置的安全认证后方可正常访问,否则无法正常发送请求。这里为了演示Security的CSRF实际默认防护效果,编写一个页面进行演示说明。
(1)创建数据修改页面。打开项目resources/templates目录,在该目录下创建一个名为csrf的文件夹,在该文件夹中编写一个模拟修改用户账号信息的Thymeleaf页面csrfTest.html用来进行CSRF测试,内容如文件1所示。
文件1 csrfTest.html
1 <!DOCTYPE html>
2 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
3 <head>
4 <meta charset="UTF-8">
5 <title>用户修改</title>
6 </head>
7 <body>
8 <div align="center">
9 <form method="post" action="/updateUser">
10 用户名: <input type="text" name="username" /><br />
11 密 码: <input type="password" name="password" /><br />
12 <button type="submit">修改</button>
13 </form>
14 </div>
15 </body>
16 </html>
文件1中,编写了一个进行用户信息修改的<form>表单,<form>标签中定义了请求方法为post,使用action属性定义了提交路径为“/updateUser”。
(2)编写后台控制层方法。在chapter07项目的com.itheima.controller的包下,创建一个用于CSRF页面请求测试的控制类CSRFController,内容如文件2所示。
文件2 CSRFController.java
1 import org.springframework.stereotype.Controller;
2 import org.springframework.web.bind.annotation.*;
3 import javax.servlet.http.HttpServletRequest;
4 @Controller
5 public class CSRFController {
6 // 向用户修改页跳转
7 @GetMapping("/toUpdate")
8 public String toUpdate() {
9 return "csrf/csrfTest";
10 }
11 // 用户修改提交处理
12 @ResponseBody
13 @PostMapping(value = "/updateUser")
14 public String updateUser(@RequestParam String username, @RequestParam String password,
15 HttpServletRequest request) {
16 System.out.println(username);
17 System.out.println(password);
18 String csrf_token = request.getParameter("_csrf");
19 System.out.println(csrf_token);
20 return "ok";
21 }
22 }
文件2中,编写的toUpdate()方法用于向用户修改页面跳转,updateUser()方法用于对用户修改提交数据处理。其中,在updateUser()方法中只是演示了获取的请求参数,没有具体的业务实现。
(3)CSRF默认防护效果测试。重启chapter07项目,通过浏览器访问“http://localhost:8080/toUpdate
”用户修改页面,由于前面配置了请求拦截,会先被拦截跳转到用户登录页面。在用户登录页面输入正确的用户信息后,就会自动跳转到用户修改页面,效果如图1所示。
图1 访问用户修改页面效果
在图1所示的用户修改页面中,随意输入修改后的用户名和密码,单击【修改】按钮进行数据提交,效果如图2所示。
图2 用户修改提交测试效果
从图2可以看出,在代码业务逻辑没有错误的情况下,表单中正确提交POST的请求数据被拦截,出现了403和Forbidden(禁止)的错误提示信息,而后台也没有任何响应。这说明整合使用的Spring Security安全框架默认启用了CSRF安全防护功能,而上述示例被拦截的本质原因就是数据修改请求中没有携带CSRF Token(CSRF令牌)相关的参数信息,所以被认为是不安全的请求。
通过上述示例可以看出,在整合Spring Security安全框架后,项目默认启用了CSRF安全防护功能,项目中所有涉及到数据修改方式的请求都会被拦截。针对这种情况,可以有两种处理方式:一种方式是直接关闭Security默认开启的CSRF防御功能;另一种方式就是配置Security需要的CSRF Token。
如果使用关闭Security默认开启的CSRF防御功能的配置非常简单,打开配置类SecurityConfig,在重写的configure(HttpSecurity http)方法中进行关闭配置即可,示例代码如下。
@Override
protected void configure(HttpSecurity http) throws Exception {
// 可以关闭Spring Security默认开启的CSRF防护功能
http.csrf().disable();
...
}
上述示例中展示了关闭CSRF防御功能的配置方式,其他代码示例无需变动。需要说明的是,这种直接关闭CSRF防御的方式简单粗暴,不太推荐使用,如果强行关闭后网站可能会面临CSRF攻击的危险。
Spring Security针对不同类型的数据修改请求提供了不同方式的CSRF Token配置,主要包括有:针对Form表单数据修改的CSRF Token配置和针对Ajax数据修改请求的CSRF Token配置。下面,分别对这两种配置方式进行讲解。
2.针对Form表单数据修改的CSRF Token配置
针对Form表单类型的数据修改请求,Security支持在Form表单中提供一个携带CSRF Token信息的隐藏域,与其他修改数据一起提交,这样后台就可以获取并验证该请求是否为安全的,示例代码如下。
<form method="post" action="/updateUser">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
用户名: <input type="text" name="username" /> <br />
密 码: <input type="password" name="password" /> <br />
<button type="submit">修改</button>
</form>
上述代码中,Form表单中的< input>隐藏标签携带了Security提供的CSRF Token信息。其中,th:name="${_csrf.parameterName}"会获取Security默认提供的CSRF Token对应的key值_csrf,th:value="${_csrf.token}"会获取Security默认随机生成的CSRF Token对应的value值。在Form表单中添加上述CSRF配置后,无需其他配置就可以正常实现数据修改请求,后台配置的Security会自动获取并识别请求中的CSRF Token信息并进行用户信息验证,从而判断是否安全。
需要说明的是,针对于Thymeleaf模板页面中的Form表单数据修改请求,除了可以使用上述示例方式显示地配置CSRF Token信息提交数据修改请求外,还可以使用Thymeleaf模板的th:action属性配置CSRF Token信息,示例代码如下。
<form method="post" th:action="@{/updateUser}">
用户名: <input type="text" name="username" /> <br />
密 码: <input type="password" name="password" /> <br />
<button type="submit">修改</button>
</form>
上述代码中,使用了Thymeleaf模板的th:action属性配置了Form表单数据修改后的请求路径,而在表单中并没有提供携带CSRF Token信息的隐藏域,但仍然可以正常的执行数据修改请求。这是因为使用Thymeleaf模板的th:action属性配置请求时,会默认携带CSRF Token信息,无需开发者手动添加,这也解释了前面编写的login.html页面进行用户登录时为何可以正常执行的原因。
3.针对Ajax数据修改请求的CSRF Token配置
对于Ajax类型的数据修改请求来说,Security提供了通过添加HTTP header头信息的方式携带CSRF Token信息进行请求验证。
首先,在页面<head>标签中添加<meta>子标签,并配置CSRF Token信息,示例代码如下。
<html>
<head>
<!-- 获取CSRF Token -->
<meta name="_csrf" th:content="${_csrf.token}"/>
<!-- 获取CSRF头,默认为X-CSRF-TOKEN -->
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
</head>
...
上述代码中,在<head>标签中添加了两个<meta>子标签,分别来设置CSRF Token信息的属性头和具体生成的Security Token值信息。其中,在HTTP header头信息中携带的CSRF请求头header参数的默认值为X-CSRF-TOKEN,而请求头CSRF header对应的CSRF Token值也是随机生成的。
然后,在具体的Ajax请求中获取<meta>子标签中设置的CSRF Token信息并绑定在HTTP请求头中进行请求验证,示例代码如下。
$(function () {
// 获取<meta>标签中封装的CSRF Token信息
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
// 将头中的CSRF Token信息进行发送
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
});
上述代码中,首先获取了<meta>子标签中设置的CSRF Token信息,然后将这CSRF Token信息进行请求发送,从而向后台进行用户验证。
至此,关于用户授权管理中Spring Security框架的CSRF防护功能就已经讲解完毕,由于CSRF防护配置涉及到多种不同的情况,所以这里只是进行了配置示例展示,具体使用将会在最后的项目章节进行具体的演示说明。