Spring Boot微服务跨调用异常:500错误与Keycloak类型转换问题
问题根源
跨服务调用时,management服务没有将当前请求的Keycloak认证令牌传递给user服务,导致user服务的SecurityContext中没有正确的KeycloakPrincipal,而是一个字符串类型的默认标识(比如匿名用户或令牌解析失败的结果),触发了ClassCastException。
修复方案
1. 给management服务的RestTemplate添加令牌传递拦截器
在management服务中实现一个拦截器,自动把当前请求的Bearer令牌附加到RestTemplate的调用请求头里:
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class TokenRelayInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { var auth = SecurityContextHolder.getContext().getAuthentication(); if (auth instanceof JwtAuthenticationToken jwtToken) { request.getHeaders().set(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken.getToken().getTokenValue()); } return execution.execute(request, body); } }
然后配置RestTemplate时注入这个拦截器:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; import java.util.Collections; @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(TokenRelayInterceptor tokenRelayInterceptor) { RestTemplate restTemplate = new RestTemplate(); restTemplate.setInterceptors(Collections.singletonList(tokenRelayInterceptor)); return restTemplate; } }
2. 检查user服务的认证配置
确保user服务的Security配置正确处理传入的JWT令牌,启用OAuth2资源服务器功能:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2.jwt()); return http.build(); } }
3. 给user服务的Principal转换增加类型校验
在infoUser方法里先判断Principal类型再转换,避免强制转换失败:
import org.keycloak.KeycloakPrincipal; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/v1/users") public class UserController { @GetMapping("/info") public String infoUser() { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (!(auth.getPrincipal() instanceof KeycloakPrincipal<?> keycloakPrincipal)) { throw new IllegalStateException("无法获取有效的用户认证信息,请检查令牌传递是否正常"); } return keycloakPrincipal.getKeycloakSecurityContext().getToken().getPreferredUsername(); } }
验证操作
- 重启management和user两个服务
- 再次调用
http://localhost:9002/api/v1/user_role/alladvertisements,确认不再返回500错误 - 查看user服务的日志,确认
auth.getPrincipal()已正确解析为KeycloakPrincipal
内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu
相关产品推荐
相关产品推荐

