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

使用RestTemplate调用微服务时WebAuthenticationDetails转SimpleKeycloakAccount异常

问题:使用RestTemplate调用微服务时Keycloak认证类型转换异常

环境与问题概述

  • 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。

具体解决步骤

  1. 修改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";
        }
    }
    
  2. 目标服务增加访问权限校验
    在目标服务的/info接口添加注解,确保仅Keycloak认证用户可访问:

    @GetMapping("/info")
    @PreAuthorize("isAuthenticated()")
    public ResponseEntity<?> infoUser(){
        // 原有业务代码
    }
    
  3. 校验依赖与配置一致性

    • 确认调用方和目标服务都引入了与Keycloak 18.0.2匹配的Spring Security依赖
    • 检查两个服务的配置文件(application.yml/application.properties)中Keycloak的realm、client-id、auth-server-url等参数配置一致

内容的提问来源于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 04:54:16