Spring Security ③ CSRF
网站的安全是个不容忽视的问题,一不小心就会被人攻击。今天来聊一个平时不太容易关注到的安全问题。
首先咱们还得从登录说起。
网站怎么知道你的身份的
现在的大多数网站都需要登录,登录的过程就是输入账号、密码,或者通过短信验证来确认身份。
那么登录之后呢?总不能你每次在页面上点击一个按钮,都要输入一次密码吧?
这就需要用到一些认证(authentication)机制。认证机制其实非常多,常用的有密码认证、验证码认证、会话认证、token认证(token又分好多种...)
一次讲不完,咱今天先聊一聊会话(session)认证机制。
在你用密码或验证码登录以后,在服务器端会分配一个session id,只要你还未登出,或者session还未过期,这个session id就是有效的。下次请求带上这个session id就可以证明你是你。
session id通过HTTP请求的响应带回来,保存到cookie中,后续只要还是这个网站的请求,都会自动带上cookie的内容。
乍一看这个流程没啥毛病,但黑客乐坏了,这里藏着一个巨大的CSRF漏洞。
CSRF是什么
Cross-Site Request Forgery 跨站请求伪造,黑客诱导用户访问恶意网站,并在用户不知情的情况下,在恶意网站上植入一些恶意请求,达到攻击目的。

CSRF攻击原理
- 用户正常登录网站A,获得合法Cookie
- 用户访问恶意网站B(含恶意代码)
- 恶意网站B -> 正规网站A,发起伪造请求(自动携带用户Cookie)
- 正规网站A接收到Cookie中的session id,校验通过,执行非预期操作(如转账/改密码)
这里的漏洞在于浏览器发起一个http请求时会自动带上Cookie,服务器在收到这个请求时,直接使用里面的session id进行认证。
CSRF怎么防
不要随便开启允许跨域请求
漏洞的根源在于网站B直接请求了网站A的服务器,通常情况下,如果开启了禁止跨域访问,网站B就不能跨域访问网站A的服务器了。
但总有一些例外情况:
- 通过超链接直接跳转
<a href="a.com/account/transfer..."> - 页面重定向
window.location = "a.com/..." - 预渲染
<link rel="prerender" href="a.com/..."> - img标签
<img src="a.com/..."> - 直接通过form表单提交数据
以上情况浏览器并不会当做是跨域访问
有风险的操作不要用GET请求
可以发现除了form表单以外,其他几种都是直接发起GET请求,高危操作如果使用GET请求,就很容易在这里被钻漏洞
设置Cookie的SameSite属性
SameSite属性可以指定Cookie是否允许跨站提交,但即使设置SameSite为Strict也并不能防止CSRF,这里只是多写一个知识点
设置Cookie为HttpOnly
如果Cookie设置了HttpOnly,Cookie就只能在http请求中自动发送,js就没办法读取Cookie的值
使用CSRF token
假如高危接口都使用GET以后,还有form表单这一种情况需要处理。form表单支持GET和POST而且不会被判定为跨域访问。
相关信息
所以在现行的架构中form表单已经很少在使用了,一个是存在CSRF的风险,二是样式不好调,三是前后端分离架构基本用不到form表单
此外有部分风险接口也并不一定会用POST,例如logout接口有可能还是会考虑使用GET。
针对以上风险,最好的解决办法就是使用CSRF token,流程如下
补充说明:
- csrf token是跟着session走的,登录时也需要验证token,但这个token在登录后就会失效,因为登录成功后session就变了
- token返回给浏览器后还是需要先保存在cookie中
- 在发起其他请求时从cookie中读出,再设置到http header上。这么做是因为cookie是可以被恶意网站利用的,但http header却不允许被跨域设置
- 服务端除了验证session id,还需要从http header中校验csrf token
这么做为什么能防CSRF?很重要的一点是token要设置到header中,而攻击者不能跨域修改header;token能不能伪造?理论上有可能可以从cookie中读取到(如果cookie没有设置HttpOnly),但由于恶意网站无法篡改http header,所以依然是安全的。
注意
数据在网络传输的过程中依然有可能被中间节点篡改,但那已经不是CSRF要讨论的范围了。
在Spring security中集成CSRF校验
SpringBoot集成CSRF很简单,需要先引入security starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>xxx</version>
</dependency>然后只需新增如下配置即可
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
.anyRequest().authenticated())
...;
return http.build();
}
}Spring Security的配置非常多,其他配置可以到Spring官网查询
前端需要配合改造:
- 需要从
/csrf请求(在登录前)或cookie:XSRF-TOKEN中(登录后)获取token值 - 用js设置http header: X-XSRF-TOKEN
服务端Spring Security会根据配置对请求自动进行csrf校验
哪些请求要进行CSRF校验
理论上说,查询类请求可以不用加,其他请求都加上就对了!
提示
但是但是但是,咱们聊了这么多,前提都是因为用了session认证机制引起的,感觉这种认证方式好多问题的样子,有没有更先进的?当然有!下期再聊。