Keycloak-SpringBoot Adapter调用/sso/login接口出现无限循环问题求助
问题背景
现有基于JavaScript(JQuery)开发的前端应用,向后端集成了Keycloak SpringBoot Adapter的Spring Boot服务发送API请求,按以下步骤操作后会出现登录异常:
- 用户完成登录后,通过Keycloak管理控制台注销所有Realm会话
- 重启Spring Boot应用服务
- 浏览器保留上次登录的会话Cookie未做修改
错误日志
162708 2021-09-14 14:47:10,070 [ajp-nio-0.0.0.0-8009-exec-1] DEBUG org.keycloak.adapters.PreAuthActionsHandler ?:? - adminRequest https://[my-server]/sso/login?state=60f81726-6bec-470a-90b2-2f62dfb2d2f1&session_state=f4593998-e3cb-444a-94b6-1e53ab77267d&code=85df1b52-246e-4839-9b7a-c481e1e577c2.f4593998-e3cb-444a-94b6-1e53ab77267d.442a440e-6df0-40c7-b8cb-3a52123430a0 162708 2021-09-14 14:47:10,070 [ajp-nio-0.0.0.0-8009-exec-1] DEBUG org.keycloak.adapters.PreAuthActionsHandler ?:? - checkCorsPreflight https://[my-server]/sso/login?state=60f81726-6bec-470a-90b2-2f62dfb2d2f1&session_state=f4593998-e3cb-444a-94b6-1e53ab77267d&code=85df1b52-246e-4839-9b7a-c481e1e577c2.f4593998-e3cb-444a-94b6-1e53ab77267d.442a440e-6df0-40c7-b8cb-3a52123430a0 162708 2021-09-14 14:47:10,070 [ajp-nio-0.0.0.0-8009-exec-1] DEBUG org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter ?:? - Attempting Keycloak authentication 162708 2021-09-14 14:47:10,070 [ajp-nio-0.0.0.0-8009-exec-1] TRACE org.keycloak.adapters.RequestAuthenticator ?:? - --> authenticate() 162709 2021-09-14 14:47:10,071 [ajp-nio-0.0.0.0-8009-exec-1] TRACE org.keycloak.adapters.RequestAuthenticator ?:? - try bearer 162709 2021-09-14 14:47:10,071 [ajp-nio-0.0.0.0-8009-exec-1] TRACE org.keycloak.adapters.RequestAuthenticator ?:? - try query parameter auth 162709 2021-09-14 14:47:10,071 [ajp-nio-0.0.0.0-8009-exec-1] TRACE org.keycloak.adapters.RequestAuthenticator ?:? - try oauth 162709 2021-09-14 14:47:10,071 [ajp-nio-0.0.0.0-8009-exec-1] DEBUG org.keycloak.adapters.springsecurity.token.SpringSecurityTokenStore ?:? - Checking if org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator@bd9ec13 is cached 162709 2021-09-14 14:47:10,071 [ajp-nio-0.0.0.0-8009-exec-1] DEBUG org.keycloak.adapters.OAuthRequestAuthenticator ?:? - there was a code, resolving 162709 2021-09-14 14:47:10,071 [ajp-nio-0.0.0.0-8009-exec-1] DEBUG org.keycloak.adapters.OAuthRequestAuthenticator ?:? - checking state cookie for after code 162709 2021-09-14 14:47:10,071 [ajp-nio-0.0.0.0-8009-exec-1] DEBUG org.keycloak.adapters.OAuthRequestAuthenticator ?:? - ** reseting application state cookie 162727 2021-09-14 14:47:10,089 [ajp-nio-0.0.0.0-8009-exec-1] ERROR org.keycloak.adapters.OAuthRequestAuthenticator ?:? - failed to turn code into token 162727 2021-09-14 14:47:10,089 [ajp-nio-0.0.0.0-8009-exec-1] ERROR org.keycloak.adapters.OAuthRequestAuthenticator ?:? - status from server: 400 162728 2021-09-14 14:47:10,090 [ajp-nio-0.0.0.0-8009-exec-1] ERROR org.keycloak.adapters.OAuthRequestAuthenticator ?:? - {"error":"invalid_grant","error_description":"Code not valid"} 162728 2021-09-14 14:47:10,090 [ajp-nio-0.0.0.0-8009-exec-1] DEBUG org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter ?:? - Auth outcome: FAILED 162740 2021-09-14 14:47:10,102 [ajp-nio-0.0.0.0-8009-exec-1] TRACE org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter ?:? - Failed to process authentication request org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Invalid authorization header, see WWW-Authenticate header for details at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter.attemptAuthentication(KeycloakAuthenticationProcessingFilter.java:162) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:222) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:96) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
抓包结果

相关配置
Spring Security配置
@Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); // When using authenticated() instead of hasRole() the authentication flow // returns a 401:Unauthenticated http .authorizeRequests() .antMatchers(AUTH_CHECK_LIST).hasRole("mcd-role") .anyRequest().authenticated(); // If CSRF is enabled then POST requests return 303:Forbidden http.csrf().disable(); // Allow executing Multipart upload requests http.headers().frameOptions().sameOrigin(); }
Keycloak配置参数
keycloak.auth-server-url=https://[my-server]:8543/auth keycloak.realm=AUTOTEST_PG keycloak.ssl-required=external keycloak.resource=mcd-client keycloak.public-client=true keycloak.principal-attribute=preferred_username keycloak.cors=true
问题根因
- OAuth2授权码本身是一次性有效且和用户会话绑定的,你在Keycloak控制台注销了所有会话后,旧的授权码已经完全失效,用旧code换取token自然会返回
invalid_grant: Code not valid错误 - Spring Boot重启后,Keycloak Adapter本地缓存的会话状态、state参数对应上下文全部丢失,浏览器留存的旧Cookie和当前服务端状态不匹配,默认配置没有异常跳转逻辑,就会出现反复重定向的死循环
- 现有Spring Security配置继承了Keycloak默认的失败处理逻辑,只会返回401,不会自动清理客户端无效Cookie,也不会触发重新授权流程
解决方案
1. 配置认证失败处理器(核心修复)
在Spring Security配置中添加异常处理逻辑,认证失败时自动清理无效Cookie,并重定向到入口重新走授权流程:
@Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); http .authorizeRequests() .antMatchers(AUTH_CHECK_LIST).hasRole("mcd-role") .anyRequest().authenticated() .and() // 添加异常处理逻辑 .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> { // 清理所有Keycloak相关旧Cookie和JSESSIONID Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().startsWith("KEYCLOAK_") || cookie.getName().equals("JSESSIONID")) { cookie.setValue(""); cookie.setPath("/"); cookie.setMaxAge(0); response.addCookie(cookie); } } } // 重定向到应用入口重新发起授权 response.sendRedirect("/"); }); http.csrf().disable(); http.headers().frameOptions().sameOrigin(); }
2. 前端增加未授权拦截逻辑
在JQuery全局配置中添加错误拦截,遇到401、400错误时清理本地缓存并刷新页面:
$(document).ajaxError(function(event, jqxhr, settings, thrownError) { if (jqxhr.status === 401 || jqxhr.status === 400) { localStorage.clear(); sessionStorage.clear(); window.location.href = window.location.origin; } });
3. 可选优化
在Keycloak管理控制台对应客户端的「高级设置」中,将授权码有效期(Authorization Code Lifespan)调整为1分钟,降低旧授权码被复用的概率。
临时解决方案
用户遇到该问题时,手动清理浏览器Cookie或按Ctrl+F5强制刷新页面即可恢复正常。
内容的提问来源于stack exchange,提问作者saavedrah
相关产品推荐
相关产品推荐

