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

