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

技术问询:如何使用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。以下是完整实现:

步骤说明

  1. 解析服务账号JSON凭据,提取client_email和private_key
  2. 构造JWT的Header(RS256算法)和Payload(包含权限范围、过期时间等)
  3. 用服务账号私钥对JWT进行RS256签名
  4. 发送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/folders API,权限范围需要调整为https://www.googleapis.com/auth/cloud-platform
  • 记得处理Token过期的情况,建议缓存Token并在过期前重新生成

内容的提问来源于stack exchange,提问作者Maninder Chhabra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 11:04:08