问题描述
6版本(starter 3版本)之前,我们可以简单地通过如下代码开启CSRF验证,并且将token通过Cookie提供给前端
http.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
;
默认设置下前端可以从XSRF-TOKEN
字段拿到token,然后把它丢到请求header中的X-XSRF-TOKEN
字段中即可
6版本之后会发现单纯这样开启时按照以往的请求方式会被拦截并302到登陆界面,此时,cookie里有XSRF-TOKEN
字段,而302的网页里也有一段
<input name="_csrf" type="hidden" value="xsAQ1pzqT6wQB6SZxE8QXANbvNNXV483n1xCBpbhmQKqRRO_p_J04avffM89Y5SgoWIkPTE4kbFgZLoa_G91ZKXQrDuddCeH" />
而网页里的这个才是真正的 token ,cookie里的那个是无效的。
解决方案
将上述开启代码改为
http.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
;
问题追踪
CSRF验证由CsrfFilter
类负责,查看该类的doFilterInternal
方法,以debug模式运行发现这里的actualToken
为null
导致匹配不成功
String actualToken = this.requestHandler.resolveCsrfTokenValue(request, csrfToken);
其中requestHandler
的定义为
private CsrfTokenRequestHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();
查看该方法的实现
@Override
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
String actualToken = super.resolveCsrfTokenValue(request, csrfToken);
return getTokenValue(actualToken, csrfToken.getToken());
}
debug模式运行发现这里的在执行了父类的方法后actualToken
是有值的,那么罪魁祸首就是getTokenValue
这个方法,这是一个静态方法,老实说它写了啥我看不懂
但是我们发现XorCsrfTokenRequestAttributeHandler
这个类有一个父类CsrfTokenRequestAttributeHandler
,它没有重写resolveCsrfTokenValue
方法,而且有无参构造方法,那么直接new一个覆盖原定义就好了。