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

如何将GCP Secret Manager密钥读取为文件(如GOOGLE_APPLICATION_CREDENTIALS JSON)?

问题描述

之前我会将GOOGLE_APPLICATION_CREDENTIALS指向存储Firebase服务账号(SA)凭证的文件,例如GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json。对应的初始化代码如下:

@Value("${api-key.google.application-credentials}")
private String googleCredentials;


@Bean
public FirebaseApp firebaseApp() throws IOException {

    LOGGER.info("Initializing Firebase.");

    FileInputStream serviceAccount = new FileInputStream(googleCredentials);
    FirebaseOptions options = FirebaseOptions.builder()
            .setCredentials(GoogleCredentials.fromStream(serviceAccount))
            .build();

    if (FirebaseApp.getApps().isEmpty()) {
        return FirebaseApp.initializeApp(options);
    }

    return FirebaseApp.getApps().get(0);
}

现在部署阶段决定采用GCP Secret Manager,想让上述代码既能适配以下配置:

api-key:
  google:
    application-credentials: ${sm://GOOGLE_APPLICATION_CREDENTIALS}

也能兼容原来的文件路径配置:

api-key:
  google:
    application-credentials: ${GOOGLE_APPLICATION_CREDENTIALS:google-credentials.json}

不想用创建临时文件来传递给GoogleCredentials.fromStream()的方式,有没有更合适的实现方法?


解决方案

1. 直接区分配置类型处理

GoogleCredentials支持直接从字节流读取JSON内容,无需转成文件。可以通过判断配置值的格式,分别处理文件路径和Secret Manager密钥:

@Value("${api-key.google.application-credentials}")
private String googleCredentials;

@Autowired
private SecretManagerServiceClient secretManagerServiceClient;

@Bean
public FirebaseApp firebaseApp() throws IOException {
    LOGGER.info("Initializing Firebase.");

    InputStream credentialsStream;
    if (googleCredentials.startsWith("sm://")) {
        // 处理Secret Manager引用:提取密钥名并获取JSON内容
        String secretName = googleCredentials.substring(5);
        AccessSecretVersionResponse response = secretManagerServiceClient.accessSecretVersion(secretName);
        String secretJson = response.getPayload().getData().toStringUtf8();
        credentialsStream = new ByteArrayInputStream(secretJson.getBytes(StandardCharsets.UTF_8));
    } else {
        // 处理本地文件路径
        credentialsStream = new FileInputStream(googleCredentials);
    }

    FirebaseOptions options = FirebaseOptions.builder()
            .setCredentials(GoogleCredentials.fromStream(credentialsStream))
            .build();

    return FirebaseApp.getApps().isEmpty() ? FirebaseApp.initializeApp(options) : FirebaseApp.getApps().get(0);
}

2. 借助Spring Cloud GCP自动解析

如果项目使用Spring Cloud GCP,框架会自动将${sm://xxx}格式的配置解析为Secret Manager的密钥值(JSON字符串)。此时可以通过判断配置值是否为合法JSON来简化逻辑:

@Value("${api-key.google.application-credentials}")
private String googleCredentials;

@Bean
public FirebaseApp firebaseApp() throws IOException {
    LOGGER.info("Initializing Firebase.");

    InputStream credentialsStream;
    try {
        // 尝试解析JSON,判断是否是Secret返回的JSON字符串
        new JSONObject(googleCredentials);
        credentialsStream = new ByteArrayInputStream(googleCredentials.getBytes(StandardCharsets.UTF_8));
    } catch (JSONException e) {
        // 非JSON格式,当作文件路径处理
        credentialsStream = new FileInputStream(googleCredentials);
    }

    FirebaseOptions options = FirebaseOptions.builder()
            .setCredentials(GoogleCredentials.fromStream(credentialsStream))
            .build();

    return FirebaseApp.getApps().isEmpty() ? FirebaseApp.initializeApp(options) : FirebaseApp.getApps().get(0);
}

3. 自定义转换器解耦逻辑

创建Spring类型转换器,自动将配置值转为对应InputStream,代码更简洁:

@Component
public class CredentialsInputStreamConverter implements Converter<String, InputStream> {

    private final SecretManagerServiceClient secretManagerServiceClient;

    public CredentialsInputStreamConverter(SecretManagerServiceClient secretManagerServiceClient) {
        this.secretManagerServiceClient = secretManagerServiceClient;
    }

    @Override
    public InputStream convert(String source) {
        try {
            if (source.startsWith("sm://")) {
                String secretName = source.substring(5);
                AccessSecretVersionResponse response = secretManagerServiceClient.accessSecretVersion(secretName);
                return new ByteArrayInputStream(response.getPayload().getData().toStringUtf8().getBytes(StandardCharsets.UTF_8));
            } else {
                return new FileInputStream(source);
            }
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to resolve credentials from: " + source, e);
        }
    }
}

之后直接注入InputStream即可:

@Value("${api-key.google.application-credentials}")
private InputStream googleCredentialsStream;

@Bean
public FirebaseApp firebaseApp() throws IOException {
    LOGGER.info("Initializing Firebase.");

    FirebaseOptions options = FirebaseOptions.builder()
            .setCredentials(GoogleCredentials.fromStream(googleCredentialsStream))
            .build();

    return FirebaseApp.getApps().isEmpty() ? FirebaseApp.initializeApp(options) : FirebaseApp.getApps().get(0);
}

内容的提问来源于stack exchange,提问作者Stefan Falk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 13:47:05