技术问询:如何使用Java通过REST API从服务账号凭据创建Access Token,以及无GCP Java客户端库限制下Java/Spring调用REST API查询文件夹列表的示例参考
解答:服务账号凭据生成Access Token及GCP文件夹列表查询(无客户端库)
我来帮你一步步解决这两个问题——先搞定服务账号生成Access Token,再用这个Token调用REST API查询文件夹列表,全程不用GCP官方客户端库,纯Java/Spring实现。
一、用Java通过REST API生成服务账号Access Token
要生成Access Token,我们需要基于服务账号的JSON凭据构造JWT并签名,然后调用GCP的OAuth2 token端点获取Token。以下是完整实现:
步骤说明
- 解析服务账号JSON凭据,提取
client_email和private_key - 构造JWT的Header(RS256算法)和Payload(包含权限范围、过期时间等)
- 用服务账号私钥对JWT进行RS256签名
- 发送POST请求到GCP Token端点,获取Access Token
示例代码
import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.security.Signature; public class ServiceAccountTokenGenerator { private static final String TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"; private static final String JWT_ALG = "RS256"; private static final String JWT_TYP = "JWT"; // 替换为你的服务账号JSON文件路径 private static final String SERVICE_ACCOUNT_JSON_PATH = "/path/to/your/service-account.json"; // 替换为你需要的权限范围,比如Cloud Storage只读权限 private static final String SCOPE = "https://www.googleapis.com/auth/cloud-platform.read-only"; public static void main(String[] args) throws Exception { // 1. 解析服务账号JSON ObjectMapper objectMapper = new ObjectMapper(); JsonNode serviceAccount = objectMapper.readTree(ServiceAccountTokenGenerator.class.getResourceAsStream(SERVICE_ACCOUNT_JSON_PATH)); String clientEmail = serviceAccount.get("client_email").asText(); String privateKeyPem = serviceAccount.get("private_key").asText(); // 2. 处理私钥(去除PEM格式标记) String privateKeyStr = privateKeyPem.replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replaceAll("\\s", ""); byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyStr); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); // 3. 构造JWT Header和Payload long now = System.currentTimeMillis() / 1000; long expiry = now + 3600; // Token有效期1小时(最大允许3600秒) // Header Map<String, Object> header = new HashMap<>(); header.put("alg", JWT_ALG); header.put("typ", JWT_TYP); String headerBase64 = Base64.getUrlEncoder().withoutPadding().encodeToString(objectMapper.writeValueAsString(header).getBytes(StandardCharsets.UTF_8)); // Payload Map<String, Object> payload = new HashMap<>(); payload.put("iss", clientEmail); payload.put("scope", SCOPE); payload.put("aud", TOKEN_ENDPOINT); payload.put("exp", expiry); payload.put("iat", now); String payloadBase64 = Base64.getUrlEncoder().withoutPadding().encodeToString(objectMapper.writeValueAsString(payload).getBytes(StandardCharsets.UTF_8)); // 4. 签名JWT String signatureInput = headerBase64 + "." + payloadBase64; Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(signatureInput.getBytes(StandardCharsets.UTF_8)); byte[] signatureBytes = signature.sign(); String signatureBase64 = Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes); String jwt = headerBase64 + "." + payloadBase64 + "." + signatureBase64; // 5. 请求Access Token RestTemplate restTemplate = new RestTemplate(); Map<String, String> requestBody = new HashMap<>(); requestBody.put("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"); requestBody.put("assertion", jwt); JsonTokenResponse tokenResponse = restTemplate.postForObject(TOKEN_ENDPOINT, requestBody, JsonTokenResponse.class); System.out.println("Access Token: " + tokenResponse.getAccess_token()); } // 用于解析Token响应的DTO static class JsonTokenResponse { private String access_token; private String token_type; private long expires_in; // Getters and Setters public String getAccess_token() { return access_token; } public void setAccess_token(String access_token) { this.access_token = access_token; } public String getToken_type() { return token_type; } public void setToken_type(String token_type) { this.token_type = token_type; } public long getExpires_in() { return expires_in; } public void setExpires_in(long expires_in) { this.expires_in = expires_in; } } }
注意:
- 确保你的项目中引入了
jackson-databind和spring-web依赖(Spring项目通常已经包含) - 替换
SERVICE_ACCOUNT_JSON_PATH为你的服务账号JSON文件实际路径 - 根据需求调整
SCOPE,比如如果只需要Cloud Storage权限,可以用https://www.googleapis.com/auth/devstorage.read_only
二、Spring环境下调用REST API查询文件夹列表(以Cloud Storage为例)
拿到Access Token后,我们可以调用GCP的REST API来查询文件夹。下面以Cloud Storage为例,查询指定Bucket下的所有文件夹:
示例代码
import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; public class GcpFolderLister { // 替换为你的Bucket名称 private static final String BUCKET_NAME = "your-bucket-name"; // 替换为你要查询的根文件夹前缀(如果查根目录可以留空,但要保持格式) private static final String FOLDER_PREFIX = "your-folder/"; public static void main(String[] args) throws Exception { // 这里假设你已经通过上面的代码获取到了Access Token String accessToken = "your-generated-access-token"; RestTemplate restTemplate = new RestTemplate(); String apiUrl = "https://storage.googleapis.com/storage/v1/b/{bucket}/o?delimiter=/&prefix={prefix}"; // 构造请求参数 Map<String, String> uriVariables = new HashMap<>(); uriVariables.put("bucket", BUCKET_NAME); uriVariables.put("prefix", FOLDER_PREFIX); // 设置请求头 org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders(); headers.set("Authorization", "Bearer " + accessToken); org.springframework.http.HttpEntity<String> entity = new org.springframework.http.HttpEntity<>(headers); // 调用API JsonNode response = restTemplate.exchange(apiUrl, org.springframework.http.HttpMethod.GET, entity, JsonNode.class, uriVariables).getBody(); // 解析返回结果,获取文件夹列表 System.out.println("文件夹列表:"); if (response.has("prefixes")) { for (JsonNode prefix : response.get("prefixes")) { // 去掉末尾的斜杠,得到文件夹名称 String folderName = prefix.asText().replace(FOLDER_PREFIX, "").replace("/", ""); System.out.println("- " + folderName); } } } }
关键说明
- 这个API通过
delimiter=/和prefix参数来筛选出文件夹(GCS中文件夹是通过对象前缀模拟的) - 如果要查询GCP资源管理器的组织/文件夹(不是Cloud Storage),可以调用
https://cloudresourcemanager.googleapis.com/v1/foldersAPI,权限范围需要调整为https://www.googleapis.com/auth/cloud-platform - 记得处理Token过期的情况,建议缓存Token并在过期前重新生成
内容的提问来源于stack exchange,提问作者Maninder Chhabra
相关产品推荐
相关产品推荐

