我可以不吃啊 发表于 2025-2-12 12:24:00

SpringSecurity CSRF传入正确雷同的token无法登陆

原文
前因

当我根据https://spring.io/guides/tutorials/spring-boot-oauth2 教程去实现一个oauth2demo时,点击logout始终无法成功登出,报错403,但是我检查request-header中x-xsrf-token和cookie中的XSRF-TOKEN的值雷同。https://stackoverflow.com/questions/74447118/csrf-protection-not-working-with-spring-security-6最后在这个回复中得到了解决办法。
简单总结:在Spring Security 5.8及更高版本中,默认利用XorCsrfTokenRequestAttributeHandler匹配token,这就需要前端传入的token不能是raw token,详细解决可以参考这个文档:https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript
调试过程

如何找到该问题的源头:在application.yml 中增长:
logging:
level:
    org.springframework: tracedebug时点击logout按钮,会发现控制台报错: o.s.security.web.csrf.CsrfFilter: Invalid CSRF token found for http://localhost:8080/logout 。以是我就开始一步步在CsrfFilter里进行调试。
根据报错信息,可以很直接的找到关键的方法为
public final class CsrfFilter extends OncePerRequestFilter {
                @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                        throws ServletException, IOException {
                        .....
                CsrfToken csrfToken = deferredCsrfToken.get();
                String actualToken = this.requestHandler.resolveCsrfTokenValue(request, csrfToken);
                if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
                        boolean missingToken = deferredCsrfToken.isGenerated();
                        this.logger
                                .debug(LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
                        AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken)
                                        : new MissingCsrfTokenException(actualToken);
                        this.accessDeniedHandler.handle(request, response, exception);
                        return;
                }
                filterChain.doFilter(request, response);

}
[*]首先之以是会打印Invalid CSRF token found for xxx ,是因为满足了if (!equalsConstantTime(csrfToken.getToken(), actualToken))的条件,随后检查发现csrfToken.getToken()的值正常,为Cookie里的XSRF-TOKEN。 非常的是actualToken 为空。
[*]以是进入上一行String actualToken = this.requestHandler.resolveCsrfTokenValue(request, csrfToken);的resolveCsrfTokenValue 方法中检查。这里留意的是,在默认设置下,这里的requesthandler 为 XorCsrfTokenRequestAttributeHandler ,以是需要进入到这个类查看该方法的实现。
        @Override
        public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
                String actualToken = super.resolveCsrfTokenValue(request, csrfToken);
                return getTokenValue(actualToken, csrfToken.getToken());
        }第一行super.resolveCsrfTokenValue(request, csrfToken); 调用的是父类方法,即获取request header中X-XSRF-TOKEN 的值,此处正常。第二行getTokenValue(actualToken, csrfToken.getToken()) 是将Cookie提取的token值和request header的值进行比较。进入该方法进行调试可发现actualToken 为空的缘故原由为满足了以下条件:
        private static String getTokenValue(String actualToken, String token) {
        ......
                if (actualBytes.length != tokenSize * 2) {
                        return null;
                }
        ....
        }
解释

无论是看我开头提到总结的或是观察CsrfFilter 代码,会发现默认利用 XorCsrfTokenRequestAttributeHandler ,比较时会先进行处理(问了ai,该handler期望header传入的token的格式应该为Base64(随机字节+(TOKEN ⊕ 随机字节)) ,以是才会有两倍长度比较的条件。而我们根据例子传入的普通原token。以是不能成功)。
解决

详细解决可以参考这个文档:https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript,简单来说header传入的token利用CsrfTokenRequestAttributeHandler 处理即可。
额外提一下,解决方法中.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) 这里注册自定义handler现实上是替换掉了默认的XorCsrfTokenRequestAttributeHandler ,详细实现是在CsrfConfigurer.configure(H http) 。
题外话

写这类的内容时,实在是很难把控内容的精细程度。一方面是写给我自己回顾,一方面也是盼望能资助到碰到雷同问题的人。如果写的过细,以我自己的心性来说,我肯定是没耐心读的。如果写的太粗糙,那么又无法解决问题。以是我是以我现实如何发现,调试解决这个问题的流程进行记载的,而不是对相干代码从头至尾进行讲解。
这也让我想到另一个很常见的问题,即很多人提问如何提升自己时,说到相干优秀框架的源码读不进去。这段雷同的CsrfFilter 代码,如果我没碰到这个问题,而是直接开始阅读,我肯定没耐心去调试,去搞懂很多细节。大概还是得多去尝试直接写一些demo,通过解决问题来学习。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: SpringSecurity CSRF传入正确雷同的token无法登陆