You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

抓包结果

Keycloak-loop

相关配置

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

问题根因
  1. OAuth2授权码本身是一次性有效且和用户会话绑定的,你在Keycloak控制台注销了所有会话后,旧的授权码已经完全失效,用旧code换取token自然会返回invalid_grant: Code not valid错误
  2. Spring Boot重启后,Keycloak Adapter本地缓存的会话状态、state参数对应上下文全部丢失,浏览器留存的旧Cookie和当前服务端状态不匹配,默认配置没有异常跳转逻辑,就会出现反复重定向的死循环
  3. 现有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 15:15:02