Springboot OAuth2启用CSRF后登出提示XSRF Token不匹配求助
Spring Boot OAuth2 登出时CSRF Token验证失败问题
环境配置
1. application.yml 配置
spring: security: oauth2: client: registration: github: clientId: *** clientSecret: ***
2. Gradle 依赖
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-web'
3. 安全过滤器链配置
@Slf4j @Configuration @EnableWebSecurity public class OAuth2LoginSecurityConfig { @Autowired private CustomOAuth2AuthenticationFailureHandler failureHandler; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { CookieCsrfTokenRepository cRepo = CookieCsrfTokenRepository.withHttpOnlyFalse(); http .addFilterBefore(new PrintCsrfTokenFilter(cRepo), CsrfFilter.class) .csrf(csrf -> csrf.csrfTokenRepository(cRepo)) .authorizeHttpRequests(authorize -> authorize .requestMatchers("/", "/error", "/webjars/**","/index.html").permitAll() .anyRequest().authenticated() ) .logout((logout) -> logout.logoutSuccessUrl("/").permitAll()) .oauth2Login(t -> t.failureHandler((request, response, exception) -> { log.error(exception.getMessage()); request.getSession().setAttribute("error.message", exception.getMessage()); failureHandler.onAuthenticationFailure(request, response, exception); })); return http.build(); } }
4. CSRF 调试过滤器
@Slf4j public final class PrintCsrfTokenFilter extends OncePerRequestFilter { private CsrfTokenRepository tokenRepository; public PrintCsrfTokenFilter(CsrfTokenRepository csrfTokenRepository) { Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null"); this.tokenRepository = csrfTokenRepository; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { DeferredCsrfToken deferredCsrfToken = this.tokenRepository.loadDeferredToken(request, response); CsrfToken csrfToken = deferredCsrfToken.get(); String actualToken = this.resolveCsrfTokenValue(request, csrfToken); log.info("csrfToken: {} , actualToken: {}", csrfToken.getToken(), actualToken); String xcsrfToken = request.getHeader("X-XSRF-TOKEN"); log.info("xcsrfToken Token: " + xcsrfToken); filterChain.doFilter(request, response); } private String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { Assert.notNull(request, "request cannot be null"); Assert.notNull(csrfToken, "csrfToken cannot be null"); String actualToken = request.getHeader(csrfToken.getHeaderName()); log.info("header {}: {} ", csrfToken.getHeaderName(), actualToken); if (actualToken == null) { actualToken = request.getParameter(csrfToken.getParameterName()); log.info("param {}: {} ", csrfToken.getParameterName(), actualToken); } return actualToken; } }
5. 前端页面代码
<body> <script type="text/javascript"> // Declare logout function in the global scope window.logout = function() { $.post("/logout", function() { $("#user").html(''); $(".unauthenticated").show(); $(".authenticated").hide(); }) return true; } $.ajaxSetup({ beforeSend : function(xhr, settings) { if (settings.type == 'POST' || settings.type == 'PUT' || settings.type == 'DELETE') { if (!(/^http:.*/.test(settings.url) || /^https:.*/ .test(settings.url))) { // Only send the token to relative URLs i.e. locally. xhr.setRequestHeader("X-XSRF-TOKEN", Cookies.get('XSRF-TOKEN')); } } } }); $(document).ready(function() { $.get("/user", function(data, status, xhr) { if (xhr.status == 200 && data.name != null && data.name != "") { $("#user").html(data.name); $(".unauthenticated").hide(); $(".authenticated").show(); } else { $(".authenticated").hide(); $(".unauthenticated").show(); } }).fail(function() { $(".authenticated").hide(); $(".unauthenticated").show(); }); }); </script> <div class="container"> <h1>Demo</h1> <div class="container unauthenticated"> With GitHub: <a href="/oauth2/authorization/github">click here</a> </div> <div class="container authenticated"> Logged in as: <span id="user"></span> <div> <button onClick="logout()" class="btn btn-primary">Logout</button> </div> </div> </div> </body>
问题现象
已实现GitHub OAuth2认证,但执行登出操作时始终提示CSRF Token无效,关键日志如下:
2023-07-31T18:17:31.904+08:00 INFO 10277 --- [nio-8080-exec-9] c.mark.oauth2.Demo.PrintCsrfTokenFilter : csrfToken: afc6a080-363c-4d9f-87e2-187314baf11a , actualToken: afc6a080-363c-4d9f-87e2-187314baf11a 2023-07-31T18:17:31.905+08:00 DEBUG 10277 --- [nio-8080-exec-9] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost:8080/logout
通过断点调试确认,actualToken与csrfToken.getToken()的值完全一致,但Spring Security的CsrfFilter仍判定Token无效。
浏览器请求情况:
- 登出请求详情:

- 请求头信息:

求助需求
希望排查OAuth2配置中是否存在遗漏,解决登出时的CSRF Token验证失败问题,后续会更新解决方案供大家参考学习。
内容的提问来源于stack exchange,提问作者mark ortiz
相关产品推荐
相关产品推荐

