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
相关产品推荐
相关产品推荐

