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

如何在纯Java控制台应用中获取Keycloak的Bearer Token?

好的,针对你的Java控制台批处理应用需求,Keycloak提供了两种非常适配的方案来获取Bearer Token,正好对应你提到的「固定令牌(服务账户)」和「用户名/密码」两种场景,下面我给你详细拆解每种方式的配置步骤和代码实现:

方式一:使用服务账户(无用户交互的固定令牌场景)

这种模式专门为后台服务、批处理任务这类无人值守的应用设计,不需要用户参与,令牌属于Keycloak客户端本身,是最安全的选择。

前置配置(Keycloak控制台)

  • 新建或修改一个客户端,将访问类型设置为confidential
  • 在客户端的「设置」标签页,开启「服务账户启用」选项
  • 切换到「服务账户角色」标签页,为该服务账户分配后端API所需的角色权限(确保它能访问目标REST接口)

Java代码实现

你可以用Spring的RestTemplate直接调用Keycloak的Token端点,也可以使用Keycloak官方的Java客户端库,这里给出最通用的RestTemplate示例:

首先添加必要的Maven依赖(如果是Spring Boot项目):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

然后编写获取令牌的代码:

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 ServiceAccountTokenFetcher {
    public static void main(String[] args) {
        // 替换成你的Keycloak实际配置
        String keycloakRealmUrl = "http://your-keycloak-domain/auth/realms/your-realm-name";
        String clientId = "your-service-client-id";
        String clientSecret = "your-service-client-secret"; // 从Keycloak客户端的「凭据」标签页获取

        // 构建请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        // 构建请求体,使用client_credentials授权类型
        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);

        // 调用Token端点
        RestTemplate restTemplate = new RestTemplate();
        String tokenEndpoint = keycloakRealmUrl + "/protocol/openid-connect/token";
        TokenResponse tokenResponse = restTemplate.postForObject(tokenEndpoint, request, TokenResponse.class);

        if (tokenResponse != null) {
            String bearerToken = tokenResponse.getAccessToken();
            System.out.println("获取到服务账户令牌: " + bearerToken);
            // 后续调用后端API时,在请求头中添加:Authorization: Bearer {bearerToken}
        }
    }

    // 用于解析Keycloak返回的JSON响应的实体类
    static class TokenResponse {
        private String access_token;
        private String token_type;
        private long expires_in;

        public String getAccessToken() {
            return access_token;
        }

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

        // 可按需添加其他字段的getter/setter,比如refresh_token(服务账户模式一般不需要刷新令牌)
    }
}
方式二:使用用户名/密码模式(模拟用户登录)

如果你的批处理任务需要以特定用户身份访问API,可以使用这种模式,直接用固定的用户名和密码获取用户令牌。

前置配置(Keycloak控制台)

  • 找到对应的客户端(可以复用你Angular前端的客户端,或者新建一个)
  • 在客户端的「设置」标签页,开启「Direct Access Grants Enabled」(直接访问授权启用),允许使用password授权类型

Java代码实现

同样使用RestTemplate调用Token端点,区别在于授权类型和请求参数:

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 UserPasswordTokenFetcher {
    public static void main(String[] args) {
        // 替换成你的实际配置
        String keycloakRealmUrl = "http://your-keycloak-domain/auth/realms/your-realm-name";
        String clientId = "your-client-id";
        String clientSecret = "your-client-secret"; // 如果是confidential客户端需要,public客户端可省略
        String username = "fixed-batch-user";
        String password = "fixed-user-password";

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

        MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
        requestBody.add("grant_type", "password");
        requestBody.add("client_id", clientId);
        if (clientSecret != null && !clientSecret.isEmpty()) {
            requestBody.add("client_secret", clientSecret);
        }
        requestBody.add("username", username);
        requestBody.add("password", password);
        // 可选:添加scope,比如requestBody.add("scope", "openid email");

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestBody, headers);

        RestTemplate restTemplate = new RestTemplate();
        String tokenEndpoint = keycloakRealmUrl + "/protocol/openid-connect/token";
        TokenResponse tokenResponse = restTemplate.postForObject(tokenEndpoint, request, TokenResponse.class);

        if (tokenResponse != null) {
            String bearerToken = tokenResponse.getAccessToken();
            System.out.println("获取到用户令牌: " + bearerToken);
            // 调用后端API时,同样在请求头添加Authorization: Bearer {bearerToken}
        }
    }

    static class TokenResponse {
        private String access_token;
        private String token_type;
        private long expires_in;
        private String refresh_token;

        public String getAccessToken() {
            return access_token;
        }

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

        // 可按需添加refresh_token的getter/setter,用于令牌过期后刷新
    }
}

重要注意事项

  • 优先选择服务账户模式:它不需要存储用户密码,安全性更高,更符合OAuth的设计理念
  • 密码模式的风险:密码模式(password grant)在OAuth 2.1规范中已被标记为不推荐,仅适合内部完全可信的场景,务必确保用户名和密码的安全存储(比如用环境变量、加密配置中心,绝对不要硬编码在代码里)
  • 令牌缓存:获取到的令牌有过期时间(expires_in字段,单位秒),建议缓存令牌,过期后再重新获取,避免频繁调用Keycloak的Token端点
  • 权限控制:无论是服务账户还是用户,都要确保它们拥有后端API所需的最小权限,遵循最小权限原则

内容的提问来源于stack exchange,提问作者Dennis G.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:58:30