1. 背景
使用 postman 执行get请求正常,但执行 post 请求时报错:
Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'
2. 原因分析
2.1 相关知识点:
跨域问题:
- 同一个ip、同一个网络协议、同一个端口,三者都满足就是同一个域,否则就有跨域问题
session劫持问题:
-
客户端与服务端在基于http协议在交互的数据的时候,由于http协议本身是无状态协议,引进了cookie的 方式进行记录服务端和客户端的之间交互的状态和标记。cookie里面一般会放置服务端生成的session id(会话ID)用来识别客户端访问服务端过 程中的客户端的身份标记。
-
跨域情况下, session id可能会被恶意第三方劫持,此时劫持这个session id的第三方会根据这个session id向服务器发起请求,此时服务器收到这个请求会 认为这是合法的请求,并返回根据请求完成相应的服务端更新。
什么是csrf:
- CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack” 或者Session Riding,攻击方通过伪造用户请求访问受信任站点。
2.2 原因分析
Spring Security 4.0之后,引入了CSRF,默认是开启,CSRF默认支持的方法: GET|HEAD|TRACE|OPTIONS,不支持POST:
- 如果这个http请求是get方式发起的请求,意味着它只是访问服务器 的资源,仅仅只是查询,没有更新服务器的资源,所以对于这类请求,spring security的防御策略是允许的;
- 如果这个请求是通过post请求发起的, 那么spring security是默认拦截这类请求的。因为这类请求是带有更新服务器资源的危险操作,如果恶意第三方通过劫持session id来更新 服务器资源,那会造成服务器数据被非法的篡改。
- 在默认的情况下,spring security是启用csrf 拦截功能的。这会造成:在跨域的情况下,post方式提交的请求都会被拦截无法被处理。
Spring Security为了正确的区别合法的post请求,采用了token的机制:
- 在跨域的场景下,客户端访问服务端会首先发起get请求:
- 这个get请求在到达服务端的时候,服务端的Spring security会有一个过滤 器 CsrfFilter去检查这个请求,如果这个request请求的http header里面的X-CSRF-COOKIE的token值为空的时候,服务端就好自动生成一个 token值放进这个X-CSRF-COOKIE值里面。
- 客户端在get请求的header里面获取到这个值,再发起post请求,并将这个token值带给服务端
- 在post请求的header里面设置_csrf属性的token值,提交的方式可以是ajax,也可以是放在form里面设置hidden 属性的标签里面提交给服务端;
- 服务端会根据post请求里面携带的token值进行校验,如果跟服务端发送给合法客户端的token值是一样的,那么这个post请求就可以受理和处理,如果不一样或者为空,就会被拦截。
- 由于恶意第三方可以劫持session id,而很难获取token值,所以起到了 安全的防护作用。
3. 解决方案
3.1 给postman环境中设置token
原理:在postman执行post请求前,先执行一次get请求,获取到 X-XSRF-TOKEN,放到post的header中,再执行post请求。
step1: 在Postman 中创建 Environment变量
先在 Postman 中先创建一个环境(Environment),以后的请求都基于这个环境;
再在该环境(Environment)中创建变量(如csrf-token),表示 CSRF Token 的值
step2. 创建一个 GET 请求,用来从服务端获取 CSRF Token
基于刚才的环境(Environment)创建一个 GET 请求,用来从服务端获取 token 值。注意这个请求的 Authorization tab 里面要输入用户的认证信息,在 Scripts Tab里面输入以下代码,用来从服务端的 response cookie 中取出 token 值,然后再赋给上面创建的变量。
pm.environment.set("csrf-token", decodeURIComponent(pm.cookies.get("XSRF-TOKEN")));
step3.新建 POST(或PUT/DELETE)请求,在 header 中加入 Token 值,返回给服务端
基于刚才的环境(Environment)创建待测试的 POST,PUT,DELETE 请求,在请求头 header 中加入一个X-XSRF-TOKEN
的键,它的值就是token 变量的值{{xsrf-token}}
发送 token 时,headers 中 key 为什么是X-XSRF-TOKEN
?这也是 Spring Security 中定义的默认值。另外 token 值也可以通过一个名为 _csrf
的隐藏域发送(通过最上面的报错信息中也可以看出)。
step4.再发送 POST,PUT,DELETE 请求时,服务端正常响应
注意:如果执行 POST,PUT,DELETE 请求时,报 token 值无效或者为空的错误,那么请先执行一下 GET 请求再试。
参考文档:在集成 Spring-Security 的环境中,使用 Postman 测试 RESTful 接口如何发送 CRSF Token
3.2 关闭csrf
spring Security 3默认关闭csrf,Spring Security 4默认启动了csrf。 如果不采用csrf,可禁用security的csrf。
修改方式:加上 .csrf().disable()即可。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout().logoutUrl("/logout")
.logoutSuccessUrl("/hello")
.permitAll();
http.addFilterBefore(customizeFilterSecurityInterceptor, FilterSecurityInterceptor.class)
.csrf().disable();
}