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

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();
    }
}
验证操作
  1. 重启management和user两个服务
  2. 再次调用http://localhost:9002/api/v1/user_role/alladvertisements,确认不再返回500错误
  3. 查看user服务的日志,确认auth.getPrincipal()已正确解析为KeycloakPrincipal

内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 03:45:40