CSRF 就是跨域请求伪造,英文全称是 Cross Site Request Forgery。html
这是一种很是常见的 Web 攻击方式,实际上是很好防护的,可是因为常常被不少开发者忽略,进而致使不少网站实际上都存在 CSRF 攻击的安全隐患。前端
今天松哥就来和你们聊一聊什么是 CSRF 攻击以及 CSRF 攻击该如何防护。java
本文是本系列第 18 篇,阅读本系列前面文章有助于更好的理解本文:jquery
想要防护 CSRF 攻击,那咱们得先搞清楚什么是 CSRF 攻击,松哥经过下面一张图,来和你们梳理 CSRF 攻击流程:git
其实这个流程很简单:github
CSRF 的流程大体就是这样,接下来松哥用一个简单的例子和小伙伴们展现一下 CSRF 究竟是怎么回事。web
接下来,我建立一个名为 csrf-1 的 Spring Boot 项目,这个项目至关于咱们上面所说的网上银行网站,建立项目时引入 Web 和 Spring Security 依赖,以下:spring
建立成功后,方便起见,咱们直接将 Spring Security 用户名/密码 配置在 application.properties 文件中:数据库
spring.security.user.name=javaboy spring.security.user.password=123
而后咱们提供两个测试接口:小程序
@RestController public class HelloController { @PostMapping("/transfer") public void transferMoney(String name, Integer money) { System.out.println("name = " + name); System.out.println("money = " + money); } @GetMapping("/hello") public String hello() { return "hello"; } }
假设 /transfer
是一个转帐接口(这里是假设,主要是给你们演示 CSRF 攻击,真实的转帐接口比这复杂)。
最后咱们还须要配置一下 Spring Security,由于 Spring Security 中默认是能够自动防护 CSRF 攻击的,因此咱们要把这个关闭掉:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated() .and() .formLogin() .and() .csrf() .disable(); } }
配置完成后,咱们启动 csrf-1 项目。
接下来,咱们再建立一个 csrf-2 项目,这个项目至关因而一个危险网站,为了方便,这里建立时咱们只须要引入 web 依赖便可。
项目建立成功后,首先修改项目端口:
server.port=8081
而后咱们在 resources/static 目录下建立一个 hello.html ,内容以下:
<body> <form action="http://localhost:8080/transfer" method="post"> <input type="hidden" value="javaboy" name="name"> <input type="hidden" value="10000" name="money"> <input type="submit" value="点击查看美女图片"> </form> </body>
这里有一个超连接,超连接的文本是点击查看美女图片,当你点击了超连接以后,会自动请求 http://localhost:8080/transfer
接口,同时隐藏域还携带了两个参数。
配置完成后,就能够启动 csrf-2 项目了。
接下来,用户首先访问 csrf-1 项目中的接口,在访问的时候须要登陆,用户就执行了登陆操做,访问完整后,用户并无执行登出操做,而后用户访问 csrf-2 中的页面,看到了超连接,好奇这美女到底长啥样,一点击,结果钱就被人转走了。
先来讲说防护思路。
CSRF 防护,一个核心思路就是在前端请求中,添加一个随机数。
由于在 CSRF 攻击中,黑客网站实际上是不知道用户的 Cookie 具体是什么的,他是让用户本身发送请求到网上银行这个网站的,由于这个过程会自动携带上 Cookie 中的信息。
因此咱们的防护思路是这样:用户在访问网上银行时,除了携带 Cookie 中的信息以外,还须要携带一个随机数,若是用户没有携带这个随机数,则网上银行网站会拒绝该请求。黑客网站诱导用户点击超连接时,会自动携带上 Cookie 中的信息,可是却不会自动携带随机数,这样就成功的避免掉 CSRF 攻击了。
Spring Security 中对此提供了很好的支持,咱们一块儿来看下。
Spring Security 中默认实际上就提供了 csrf 防护,可是须要开发者作的事情比较多。
首先咱们来建立一个新的 Spring Boot 工程,建立时引入 Spring Security、Thymeleaf 和 web 依赖。
项目建立成功后,咱们仍是在 application.properties 中配置用户名/密码:
spring.security.user.name=javaboy spring.security.user.password=123
接下来,咱们提供一个测试接口:
@Controller public class HelloController { @PostMapping("/hello") @ResponseBody public String hello() { return "hello"; } }
注意,这个测试接口是一个 POST 请求,由于默认状况下,GET、HEAD、TRACE 以及 OPTIONS 是不须要验证 CSRF 攻击的。
而后,咱们在 resources/templates 目录下,新建一个 thymeleaf 模版,以下:
<body> <form action="/hello" method="post"> <input type="hidden" th:value="${_csrf.token}" th:name="${_csrf.parameterName}"> <input type="submit" value="hello"> </form> </body>
注意,在发送 POST 请求的时候,还额外携带了一个隐藏域,隐藏域的 key 是 ${_csrf.parameterName}
,value 则是 ${_csrf.token}
。
这两个值服务端会自动带过来,咱们只须要在前端渲染出来便可。
接下来给前端 hello.html 页面添加一个控制器,以下:
@GetMapping("/hello") public String hello2() { return "hello"; }
添加完成后,启动项目,咱们访问 hello 页面,在访问时候,须要先登陆,登陆成功以后,咱们能够看到登陆请求中也多了一个参数,以下:
能够看到,这里也多了 _csrf
参数。
这里咱们用了 Spring Security 的默认登陆页面,若是你们使用自定义登陆页面,能够参考上面 hello.html 的写法,经过一个隐藏域传递 _csrf
参数。
访问到 hello 页面以后,再去点击按钮,就能够访问到 hello 接口了。
小伙伴们能够自行尝试在 hello.html 页面中,去掉
_csrf
参数,看看访问 hello 接口的效果。
这是 Spring Security 中默认的方案,经过 Model 将相关的数据带到前端来。
若是你的项目是先后端不分项目,这种方案就能够了,若是你的项目是先后端分离项目,这种方案很明显不够用。
若是是先后端分离项目,Spring Security 也提供了解决方案。
此次不是将 _csrf
放在 Model 中返回前端了,而是放在 Cookie 中返回前端,配置方式以下:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated() .and() .formLogin() .and() .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); } }
有小伙伴可能会说放在 Cookie 中不是又被黑客网站盗用了吗?其实不会的,你们注意以下两个问题:
_csrf
参数,而后拼接成参数传递给后端,单纯的将 Cookie 中的数据传到服务端是没用的。理解透了上面两点,你就会发现 _csrf
放在 Cookie 中是没有问题的,可是你们注意,配置的时候咱们经过 withHttpOnlyFalse 方法获取了 CookieCsrfTokenRepository 的实例,该方法会设置 Cookie 中的 HttpOnly 属性为 false,也就是容许前端经过 js 操做 Cookie(不然你就没有办法获取到 _csrf
)。
配置完成后,重启项目,此时咱们就发现返回的 Cookie 中多了一项:
接下来,咱们经过自定义登陆页面,来看看前端要如何操做。
首先咱们在 resources/static 目录下新建一个 html 页面叫作 login.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="js/jquery.min.js"></script> <script src="js/jquery.cookie.js"></script> </head> <body> <div> <input type="text" id="username"> <input type="password" id="password"> <input type="button" value="登陆" id="loginBtn"> </div> <script> $("#loginBtn").click(function () { let _csrf = $.cookie('XSRF-TOKEN'); $.post('/login.html',{username:$("#username").val(),password:$("#password").val(),_csrf:_csrf},function (data) { alert(data); }) }) </script> </body> </html>
这段 html 我给你们解释下:
_csrf
参数。服务端咱们也稍做修改,以下:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/js/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated() .and() .formLogin() .loginPage("/login.html") .successHandler((req,resp,authentication)->{ resp.getWriter().write("success"); }) .permitAll() .and() .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); } }
一方面这里给 js 文件放行。
另外一方面配置一下登陆页面,以及登陆成功的回调,这里简单期间,登陆成功的回调我就给一个字符串就能够了。你们感兴趣的话,能够查看本系列前面文章,有登陆成功后回调的详细解释。
OK,全部事情作完以后,咱们访问 login.html 页面,输入用户名密码进行登陆,结果以下:
能够看到,咱们的 _csrf
配置已经生效了。
小伙伴们能够自行尝试从登陆参数中去掉
_csrf
,而后再看看效果。
好了,今天主要和小伙伴们介绍了 csrf 攻击以及如何防护的问题。你们看到,csrf 攻击主要是借助了浏览器默认发送 Cookie 的这一机制,因此若是你的前端是 App、小程序之类的应用,不涉及浏览器应用的话,其实能够忽略这个问题,若是你的前端包含浏览器应用的话,这个问题就要认真考虑了。
好了 ,本文就说到这里,本文相关案例我已经上传到 GitHub ,你们能够自行下载:https://github.com/lenve/spring-security-samples
好啦,不知道小伙伴们有没有 GET 到呢?若是有收获,记得点个在看鼓励下松哥哦~