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

Spring Boot 3.0.2集成Keycloak 10.0.2时JWS负载解析失败求助

问题:Spring Boot 3.0.2集成Keycloak 10.0.2认证失败(重复sub字段导致JSON解析错误)

问题背景

基于Spring Boot 3.0.2开发的Thymeleaf UI项目,集成旧版本Keycloak 10.0.2时出现认证失败,集成Azure AD、Google等其他OAuth2服务均正常。

关键报错信息

com.nimbusds.jwt.proc.BadJWTException: Payload of JWS object is not a valid JSON object
    at com.nimbusds.jwt.proc.DefaultJWTProcessor.extractJWTClaimsSet(DefaultJWTProcessor.java:262) ~[nimbus-jose-jwt-9.24.4.jar!/:9.24.4]
    at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:352) ~[nimbus-jose-jwt-9.24.4.jar!/:9.24.4]
    at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:303) ~[nimbus-jose-jwt-9.24.4.jar!/:9.24.4]
    at org.springframework.security.oauth2.jwt.NimbusJwtDecoder.createJwt(NimbusJwtDecoder.java:154) ~[spring-security-oauth2-jose-6.0.1.jar!/:6.0.1]
    at org.springframework.security.oauth2.jwt.NimbusJwtDecoder.decode(NimbusJwtDecoder.java:137) ~[spring-security-oauth2-jose-6.0.1.jar!/:6.0.1]
    at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.getJwt(OidcAuthorizationCodeAuthenticationProvider.java:245) ~[spring-security-oauth2-client-6.0.1.jar!/:6.0.1]
    at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.createOidcToken(OidcAuthorizationCodeAuthenticationProvider.java:236) ~[spring-security-oauth2-client-6.0.1.jar!/:6.0.1]
    at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.authenticate(OidcAuthorizationCodeAuthenticationProvider.java:154) ~[spring-security-oauth2-client-6.0.1.jar!/:6.0.1]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-6.0.1.jar!/:6.0.1]
    at org.springframework.security.authentication.ObservationAuthenticationManager.lambda$authenticate$1(ObservationAuthenticationManager.java:53) ~[spring-security-core-6.0.1.jar!/:6.0.1]
    at io.micrometer.observation.Observation.observe(Observation.java:559) ~[micrometer-observation-1.10.3.jar!/:1.10.3]
    at org.springframework.security.authentication.ObservationAuthenticationManager.authenticate(ObservationAuthenticationManager.java:52) ~[spring-security-core-6.0.1.jar!/:6.0.1] 

问题根源

调试发现Keycloak返回的ID Token中存在两个重复的"sub"字段,导致JSON结构无效,Nimbus框架无法解析:

{
   "exp":1675788223,
   "iat":1675787923,
   "auth_time":1675787563,
   "jti":"c07d2db2-xxxx",
   "iss":"https://login.idm.xxx.com/auth/realms/appid-xxx",
   "aud":"xxxx-dev",
   "sub":"f:5ce9dxxxx:user@user.com",
   "typ":"ID",
   "azp":"xxxx-dev",
   "nonce":"dH4EL-2zxxxx",
   "session_state":"fc0a7xxxx",
   "acr":"0",
   "sub":"user@user.com",
   "groups":[ 
      "FGM-AuthUser",
      "FGM-admin"
  ]
}

解决方案

方案1:自定义JWT解码器处理重复字段

通过自定义NimbusJwtDecoder,在解析前过滤掉重复的"sub"字段,保留最后一个有效值:

@Bean
public JwtDecoder keycloakJwtDecoder(OAuth2ClientProperties oAuth2ClientProperties) {
    String issuerUri = oAuth2ClientProperties.getProvider().get("keycloak").getIssuerUri();
    NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuerUri).build();
    
    // 添加自定义逻辑,过滤重复的sub字段
    jwtDecoder.setJwtValidator(jwt -> {
        Map<String, Object> claims = new LinkedHashMap<>(jwt.getClaims());
        String lastSub = null;
        Iterator<Map.Entry<String, Object>> iterator = claims.entrySet().iterator();
        
        while (iterator.hasNext()) {
            Map.Entry<String, Object> entry = iterator.next();
            if ("sub".equals(entry.getKey())) {
                if (lastSub != null) {
                    iterator.remove();
                } else {
                    lastSub = (String) entry.getValue();
                }
            }
        }
        
        if (lastSub != null) {
            claims.put("sub", lastSub);
        }
        
        return Jwt.withTokenValue(jwt.getTokenValue())
                .headers(jwt.getHeaders())
                .claims(claims)
                .build();
    });
    
    return jwtDecoder;
}

然后在SecurityFilterChain中指定使用该解码器:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, JwtDecoder keycloakJwtDecoder) throws Exception {
    http.csrf()
        .disable()
        .authorizeHttpRequests()
        .requestMatchers(getStaticResources(coralEnabled).toArray(new String[0]))
        .permitAll()
        .anyRequest()
        .authenticated()
        .and()
        .oauth2Login(oauth2 -> oauth2
            .successHandler(kwAuthenticationSuccessHandler)
            .failureHandler(kwAuthenticationFailureHandler)
            .failureUrl("/login?error")
            .loginPage("/login")
            .permitAll()
            .userInfoEndpoint(userInfo -> userInfo.jwtDecoder(keycloakJwtDecoder))
        )
        .logout(logout -> logout
            .invalidateHttpSession(true)
            .logoutUrl("/logout")
            .logoutSuccessUrl("/login")
            .addLogoutHandler(new HeaderWriterLogoutHandler(
                new ClearSiteDataHeaderWriter(
                    ClearSiteDataHeaderWriter.Directive.CACHE,
                    ClearSiteDataHeaderWriter.Directive.COOKIES,
                    ClearSiteDataHeaderWriter.Directive.STORAGE)
            )));
    return http.build();
}

方案2:升级Keycloak版本(推荐)

Keycloak 10.0.2属于旧版本,重复sub字段的问题在后续版本中已被修复。若业务允许,建议升级到最新稳定版本(如20+),从根源解决问题的同时获得更多安全更新和功能支持。

方案3:修改Keycloak客户端配置

在Keycloak后台客户端配置中,检查是否开启了Full Scope Allowed或自定义了Token映射规则:

  • 关闭Full Scope Allowed,手动添加需要的scope,避免额外的sub字段被注入;
  • 检查客户端的Mappers配置,确保没有重复添加sub字段的映射规则。

项目配置参考

pom.xml配置

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.2</version>
    <relativePath/>
</parent>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

application.properties配置

spring.security.oauth2.client.registration.keycloak.client-id=client1
spring.security.oauth2.client.registration.keycloak.client-secret=xxxxx-xxxxx
spring.security.oauth2.client.registration.keycloak.client-name=keycloak
spring.security.oauth2.client.registration.keycloak.scope=profile,openid,email
spring.security.oauth2.client.registration.keycloak.redirect-uri=https://localhost:9097/login/oauth2/code/keycloak
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8080/auth/realms/testrealm2
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username

内容的提问来源于stack exchange,提问作者Murali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 04:44:59