这篇基本上是Spring Security Reference关于 CSRF 部分的一个笔记,只是记录一下核心逻辑。其它很多细节还是要参考官方文档。
跨站请求伪造。经典场景是:
实际上利用有 XSS 漏洞,完全可以无需受害者参与利用 javascript 而自动触发第 4 第 5 步。
这里我们把浏览器等同于用户,有些数据是用户自己可见的,有些数据是浏览器自动处理、发送而用户对这些数据是无感知的(比如 SessionId)。 银行网站以 cookie 的形式把 sessionId 发送给浏览器(set-cookie),浏览器每次请求都会再自动带上 cookie(cookie)。 上面场景第 5 步虽然“坏”表单不是源于银行网站页面而是在第三方网站的页面上,但是浏览器发现目标地址是银行网站,因此会自动带上相应的 cookie,比如 JSESSION 就会随带着被发送了。从服务器的角度看,来自第 4 步的数据与正常数据没有任何差别,因为这个业务请求便会被执行。
归根结底,这种 CSRF 的问题是因为早期的cookie设计过于简单,没有和现代浏览器同源策略等安全机制同步造成的。
一个是主流的“Synchronizer Token Pattern”方法,另一个是渐成主流的“SameSite Attribute”。
1)SameSite Attribute 这个方式实施和理解比较容易,我们先说。 服务端利用 cookie 的 SameSite 属性可以禁止浏览器从外部站点发送请求时带上 cookie。比如下面的 cookie 就不会被放在由在第三方网页发起而目标是银行网站的请求上。这就自然解决了上面的 CSRF 的问题。SameSite还可设为Strict。
2)Synchronizer Token Pattern 这个解决办法的原理是对来自浏览器的请求我们都回送一个随机数,下次浏览器再请求业务时需要在 header 里或者表单里带上这个随机数。这个随机数就是 csrf token。Spring Security就是采用的这个方式。 这个办法之所以能防范 CSRF,是因为 sessionId 来自 cookie,而 csrf token 来自 header 或者 form。相当于分别在两条不同的路径上传递。
Spring Security 模块生成 csrf token 后可放在两个地方。Spring 默认的,随机数与 sessionId 关联,放在 session 里。另一个方式:随机数放在 cookie 里。
与 session 关联比较容易理解,下次浏览器发送请求过来,服务端就可以从 header 或 form 里取出来的 csrf token 与 session 中的随机数相比较来进行判断。
通过 cookie 保存 csrf 是怎么回事呢?如果 csrf token 通过 cookie 发送给浏览器,那这个随机数不就跟 JSESSIONID 一样了会被浏览器自动传回到服务器了吗? 是的,这个通过cookie传给浏览器的 csrf token 一定会被浏览器传回给服务器,我们也正是利用这一点“保存”了 csrf token。之所以使用基于 cookie 的方式,是因为要针对前后端分离的情形让前端可以使用 javascript 获得 csrf token,并把这个 token 作为下次请求的 header 参数或者 form 参数传递给服务端。服务端所要做的就是通过对比来自 cookie 和 header/form 这两条路径的 csrf token 来做出该请求是否为CSRF的判断。
下面代码设置使用 cookie 保存csrf token,使用 cookie 传递 token 需要把 cookie 的 HttpOnly 属性设置为 false,以便让 javascript 能读到此值。
@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
}
}