본문으로 건너뛰기

Spring Boot에서 Firebase Admin SDK 통합: JSON 파일을 환경 변수로 안전하게 관리하기

· 약 5분
Jiwon Lee
111CODING

Image

Firebase Admin SDK를 Spring Boot 프로젝트에 통합하려면 일반적으로 서비스 계정 키 JSON 파일이 필요합니다. 공식 문서에서는 파일을 로컬 파일 시스템에 저장하고 이를 코드에서 직접 참조하는 방식을 제시하고 있습니다(아래).

// [https://firebase.google.com/docs/admin/setup#use-oauth-2-0-refresh-token](https://firebase.google.com/docs/admin/setup?hl=ko#use-oauth-2-0-refresh-token)

FileInputStream serviceAccount = new FileInputStream("path/to/serviceAccountKey.json");

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

FirebaseApp.initializeApp(options);

하지만 이 접근 방식은 로컬 개발 환경 및 CI/CD 환경에서 몇 가지 불편함을 초래할 수 있습니다. 파일을 특정 경로에 직접 배치해야 하며, 이로 인해 환경에 따라 파일을 관리하는 데 어려움이 발생할 수 있습니다. 이 문제를 해결하기 위해 Firebase 서비스 계정 키 JSON 파일을 환경 변수로 관리하는 방법을 모색했습니다.

첫 번째 시도로는 JSON 파일의 내용을 환경 변수로 직접 사용하는 방법을 생각했습니다. 하지만 Github Secrets에 JSON 데이터를 저장할 때, 큰따옴표가 모두 사라지는 문제가 발생하여 빌드에 실패하게 됩니다.

두번째 방법는 Github 공식문서에서 권장하는 방법(https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#storing-large-secrets)인 JSON 파일을 GPG(GNU Privacy Guard)를 사용하여 암호화 후 Repository에 업로드 한 후 복호화에 필요한 비밀번호를 Secrets에 저장하는 방법입니다. 이 방법을 사용하게 된다면 고정된 암호화된 JSON 파일을 Repository에 올려두게 되어 해당 파일이 변경될 때마다 Repository를 갱신해야 하고, 환경별로 다른 JSON 파일을 사용하는 경우, 각각의 파일을 관리하는 데 어려움이 있을 수 있습니다. 더 나아가, JSON 파일이 암호화되어 있다고 해도, Repository에 복호화 가능한 민감한 정보가 포함된 파일이 존재한다는 사실만으로도 보안 위험이 증가할 수 있습니다.

세 번째 방법으로 JSON 데이터를 BASE64로 인코딩하여 Github Secrets에 등록하는 방식입니다. 이 방법은 JSON 내용이 변경되더라도 환경 변수만 수정하면 되며, Repository에 민감한 정보를 노출할 필요도 없기 때문에 이전 방법들의 단점을 모두 보완할 수 있었습니다.

최종적으로 세번째 방법인 BASE64 인코딩을 하여 Spring boot에 적용 시키기로 결정하였습니다.

우선 JSON 파일을 BASE64 인코딩 해줍니다. 출력되는 내용을 FIREBASE_SERVICE_ACCOUNT_KEY 라는 키로 로컬 환경변수에 등록하고 Github Secrets에도 등록해 줍니다.

base64 -i serviceAccountKey.json

이제 스프링 부트에서 기존에 FileInputStream을 사용하던 부분을 환경 변수에서 값을 읽어와 Base64로 디코딩한 후, 이를 ByteArrayInputStream으로 생성하도록 변경합니다.

String base64EncodedServiceAccountKey = System.getenv("FIREBASE_SERVICE_ACCOUNT_KEY");

InputStream credentialsStream = new ByteArrayInputStream(Base64.getDecoder().decode(base64EncodedServiceAccountKey));

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

FirebaseApp.initializeApp(options);

이 방법을 통해 로컬 개발 환경과 CI/CD 파이프라인에서의 유연성을 크게 향상시킬 수 있으며, 민감한 정보를 Repository에 노출시키지 않으면서도 변경 사항을 간편하게 반영할 수 있는 최적의 솔루션이 됩니다. 결과적으로, 이 방법은 보안과 유지 관리의 복잡성을 최소화하면서 안정적인 Firebase 통합을 가능하게 하며, 다양한 환경에서 일관되게 작동하고 유지 보수에도 유리한 접근 방식입니다.