如何在纯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.
相关产品推荐
相关产品推荐

