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

如何通过Spring RestTemplate调用Keycloak保护的REST端点

用Spring RestTemplate调用受Keycloak保护的REST端点解决方案

嘿,结合你提到的JHipster创建的两个单体应用(A和B)、Keycloak认证授权的场景,我整理了两种最实用的方案,覆盖用户登录态下的调用和服务间无用户参与的调用两种情况:


场景1:前端用户已登录,从应用A后端调用应用B的受保护端点

这种情况很常见——用户在应用A的前端登录后,后端需要调用应用B的接口来获取数据。我们可以直接复用当前登录用户的JWT令牌,让RestTemplate自动携带到请求头里。

步骤1:从SecurityContext提取当前用户的JWT令牌

因为你的应用已经集成了Spring Security + Keycloak,登录用户的认证信息会存在SecurityContextHolder中。写个简单的工具方法就能拿到令牌:

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

// 可以把这个方法放到工具类或者你的服务类里
private String getCurrentUserJwtToken() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication instanceof JwtAuthenticationToken jwtAuthToken) {
        Jwt jwt = jwtAuthToken.getToken();
        return jwt.getTokenValue();
    }
    throw new IllegalStateException("当前用户未通过JWT认证,请先登录");
}

步骤2:给RestTemplate加拦截器自动带Authorization头

创建一个RestTemplate的配置类,添加拦截器,这样每次请求都会自动把令牌放到请求头里:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;

@Configuration
public class UserContextRestTemplateConfig {

    @Bean
    public RestTemplate userContextRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        
        ClientHttpRequestInterceptor authInterceptor = (request, body, execution) -> {
            String jwtToken = getCurrentUserJwtToken(); // 调用上面的工具方法
            request.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken);
            return execution.execute(request, body);
        };
        
        restTemplate.getInterceptors().add(authInterceptor);
        return restTemplate;
    }
}

步骤3:直接调用应用B的端点

在你的业务服务类里注入这个配置好的RestTemplate,直接用就行:

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class AppABusinessService {

    private final RestTemplate userContextRestTemplate;

    // 构造注入RestTemplate
    public AppABusinessService(RestTemplate userContextRestTemplate) {
        this.userContextRestTemplate = userContextRestTemplate;
    }

    public String fetchDataFromAppB() {
        // 替换成应用B的实际接口地址
        String appBProtectedUrl = "http://app-b-domain/api/your-protected-endpoint";
        return userContextRestTemplate.getForObject(appBProtectedUrl, String.class);
    }
}

场景2:应用A后端独立调用应用B(无前端用户,用客户端凭证模式)

如果是应用之间的后台服务调用(比如定时任务、数据同步),不需要用户参与,这时候可以用Keycloak的客户端凭证模式来获取令牌。

步骤1:先在Keycloak里配置好客户端权限

  • 找到应用A对应的Keycloak客户端,开启Service Accounts Enabled选项;
  • 给这个客户端分配应用B的API所需的角色/权限(确保应用B的资源服务器能识别这些权限)。

步骤2:写个工具类获取客户端凭证令牌

通过Keycloak的令牌端点,用客户端ID和密钥换取JWT:

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

public class KeycloakClientTokenProvider {

    private final String keycloakServerUrl;
    private final String realm;
    private final String clientId;
    private final String clientSecret;

    public KeycloakClientTokenProvider(String keycloakServerUrl, String realm, String clientId, String clientSecret) {
        this.keycloakServerUrl = keycloakServerUrl;
        this.realm = realm;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
    }

    public String getClientCredentialsToken() {
        String tokenEndpoint = keycloakServerUrl + "/realms/" + realm + "/protocol/openid-connect/token";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
        requestBody.add("grant_type", "client_credentials");
        requestBody.add("client_id", clientId);
        requestBody.add("client_secret", clientSecret);

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestBody, headers);
        RestTemplate restTemplate = new RestTemplate();
        
        // 用简单的POJO映射令牌响应
        TokenResponse tokenResponse = restTemplate.postForObject(tokenEndpoint, request, TokenResponse.class);
        assert tokenResponse != null;
        return tokenResponse.getAccessToken();
    }

    // 内部类,映射Keycloak返回的令牌结构
    private static class TokenResponse {
        private String access_token;

        public String getAccessToken() {
            return access_token;
        }

        public void setAccessToken(String access_token) {
            this.access_token = access_token;
        }
    }
}

步骤3:配置服务间调用的RestTemplate

在配置类里创建专用的RestTemplate,自动获取并携带客户端令牌:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ServiceToServiceRestTemplateConfig {

    @Value("${keycloak.auth-server-url}")
    private String keycloakServerUrl;

    @Value("${keycloak.realm}")
    private String realm;

    @Value("${keycloak.resource}") // 应用A的客户端ID
    private String clientId;

    @Value("${keycloak.credentials.secret}")
    private String clientSecret;

    @Bean
    public RestTemplate serviceToServiceRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        KeycloakClientTokenProvider tokenProvider = new KeycloakClientTokenProvider(keycloakServerUrl, realm, clientId, clientSecret);

        ClientHttpRequestInterceptor authInterceptor = (request, body, execution) -> {
            String clientToken = tokenProvider.getClientCredentialsToken();
            request.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + clientToken);
            return execution.execute(request, body);
        };

        restTemplate.getInterceptors().add(authInterceptor);
        return restTemplate;
    }
}

关键注意事项

  1. 令牌验证:确保应用B的ResourceServerConfiguration能正确验证令牌——JHipster通常已经配置了从Keycloak的JWKS端点自动获取公钥,不需要额外操作,但如果有自定义配置,要确保公钥正确。
  2. 令牌缓存:如果是高频调用,建议给客户端令牌加缓存(比如用Guava Cache或者Spring Cache),避免每次请求都去Keycloak拿令牌,提升性能。
  3. CORS配置:如果是前端触发的跨域调用,要在应用B的CORS配置里允许应用A的域名访问。

内容的提问来源于stack exchange,提问作者Christoph J.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 07:07:59