使用RestTemplate调用微服务时WebAuthenticationDetails转SimpleKeycloakAccount异常
环境与问题概述
- Keycloak版本:18.0.2
- 问题:通过RestTemplate实现微服务间调用时,目标服务抛出类型转换异常,无法将认证主体转换为
KeycloakPrincipal
异常信息
java.lang.ClassCastException: class org.springframework.security.web.authentication.WebAuthenticationDetails cannot be cast to class org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount (org.springframework.security.web.authentication.WebAuthenticationDetails and org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount are in unnamed module of loader 'app')
目标服务接收的认证信息
UserController | infoUser | auth toString : AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]
: UserController | infoUser | auth getPrincipal : anonymousUser
调用方RestTemplate代码
private String getRoleInfo(){ LOGGER.info("UserServiceImpl | getRoleInfo is started"); String result = this.restTemplate.getForObject(USER_BASE_URL + "/info",String.class); // 问题触发位置 LOGGER.info("UserServiceImpl | getRoleInfo | role result : " + result); return result; }
目标服务接口代码
@GetMapping("/info") public ResponseEntity<?> infoUser(){ LOGGER.info("UserController | infoUser is started"); Authentication auth = SecurityContextHolder.getContext().getAuthentication(); LOGGER.info("UserController | infoUser | auth toString : " + auth.toString()); LOGGER.info("UserController | infoUser | auth getPrincipal : " + auth.getPrincipal()); KeycloakPrincipal principal = (KeycloakPrincipal)auth.getPrincipal(); // 问题触发位置 KeycloakSecurityContext session = principal.getKeycloakSecurityContext(); AccessToken accessToken = session.getToken(); String username = accessToken.getPreferredUsername(); String email = accessToken.getEmail(); String lastname = accessToken.getFamilyName(); String firstname = accessToken.getGivenName(); String realmName = accessToken.getIssuer(); AccessToken.Access access = accessToken.getRealmAccess(); Set<String> roles = access.getRoles(); String role = roles.stream() .filter(s -> s.equals("ROLE_USER") || s.equals("ROLE_ADMIN")) .findAny() .orElse("noElement"); LOGGER.info("UserController | infoUser | username : " + username); LOGGER.info("UserController | infoUser | email : " + email); LOGGER.info("UserController | infoUser | lastname : " + lastname); LOGGER.info("UserController | infoUser | firstname : " + firstname); LOGGER.info("UserController | infoUser | realmName : " + realmName); LOGGER.info("UserController | infoUser | firstRole : " + role); return ResponseEntity.ok(role); }
解决方案
核心原因
调用方发起RestTemplate请求时,未携带Keycloak的JWT认证令牌,导致目标服务接收的是匿名认证信息,无法转换为KeycloakPrincipal。
具体解决步骤
修改RestTemplate请求,携带JWT令牌
在调用方获取当前上下文的Keycloak认证信息,提取令牌并添加到请求头:private String getRoleInfo(){ LOGGER.info("UserServiceImpl | getRoleInfo is started"); // 获取当前用户的Keycloak认证上下文 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth.getPrincipal() instanceof KeycloakPrincipal) { KeycloakPrincipal<?> keycloakPrincipal = (KeycloakPrincipal<?>) auth.getPrincipal(); KeycloakSecurityContext securityContext = keycloakPrincipal.getKeycloakSecurityContext(); String token = securityContext.getTokenString(); // 构造带认证头的请求实体 HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(token); HttpEntity<String> entity = new HttpEntity<>(headers); // 发送请求并处理响应 ResponseEntity<String> response = restTemplate.exchange( USER_BASE_URL + "/info", HttpMethod.GET, entity, String.class ); String result = response.getBody(); LOGGER.info("UserServiceImpl | getRoleInfo | role result : " + result); return result; } else { LOGGER.warn("当前用户未通过Keycloak认证"); return "unauthorized"; } }目标服务增加访问权限校验
在目标服务的/info接口添加注解,确保仅Keycloak认证用户可访问:@GetMapping("/info") @PreAuthorize("isAuthenticated()") public ResponseEntity<?> infoUser(){ // 原有业务代码 }校验依赖与配置一致性
- 确认调用方和目标服务都引入了与Keycloak 18.0.2匹配的Spring Security依赖
- 检查两个服务的配置文件(
application.yml/application.properties)中Keycloak的realm、client-id、auth-server-url等参数配置一致
内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu

