본문 바로가기
백엔드

Spring GCP Credentials 파일 여러개 적용하는 방법

by jaeyong 2022. 3. 7.

Spring에서는 GCP를 사용하고자 할때 credentials.json 파일을 property로 입력하여 사용합니다.

하지만 여러개의 credentials 파일을 사용하여 GCP를 사용하는게 불가능한데요.

(음 제가 그냥 모르고 있는 건지도...)

오늘은 여러개의 GCP Credentials 파일을 적용하는 방법에 대하여 알아보겠습니다.

 

사실 여러개의 권한을 가지는 credential 계정을 발급받으면 되지만,,, 그냥 한번 들여다 봤습니다.

 

GCP 예제는 사용하시는 분이 많으신지는 모르겠지만, 제가 사용하던 BigQuery를 기준으로 설명하겠습니다.

 

1. GCP의 기본 설정 및 BigQuery 예제

application.properties

기본적으로 GCP credentials 는 위의 코드처럼 설정합니다. 

 

package com.example.gcpmulti;

import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.FieldValue;
import com.google.cloud.bigquery.FieldValueList;
import com.google.cloud.bigquery.QueryJobConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class GCPService {
    @Autowired
    BigQuery bigquery;

    public void runQuery() throws InterruptedException {
        String query = "SELECT column FROM table;";
        QueryJobConfiguration queryConfig =
                QueryJobConfiguration.newBuilder(query).build();

        // Run the query using the BigQuery object
        for (FieldValueList row : bigquery.query(queryConfig).iterateAll()) {
            for (FieldValue val : row) {
                System.out.println(val);
            }
        }
    }
}

위의 GCPService 코드는 GCP Credentials 설정을 마치고 BigQuery를 사용하는 간단한 예제입니다.

BigQuery 객체를 DI(Dependency Injection)를 이용하여 자동으로 주입해주고 있습니다.

그리고 이 BigQuery 객체는 위의 기본으로 설정된 credentials 파일과 연결되어 GCP 로 바로 연결됩니다.

 

그럼 여기서 여러개의 credentials 파일을 적용하려면 어떻게 해야할까요?

바로 BigQuery를 사용하려는 credentials과 연결시켜 주기만 하면 됩니다.

 

2. BigQuery 객체가 Bean으로 등록되는 과정

/*
 * Copyright 2017-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.gcp.autoconfigure.bigquery;

import java.io.IOException;

import com.google.api.gax.core.CredentialsProvider;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gcp.autoconfigure.core.GcpContextAutoConfiguration;
import org.springframework.cloud.gcp.bigquery.core.BigQueryTemplate;
import org.springframework.cloud.gcp.core.DefaultCredentialsProvider;
import org.springframework.cloud.gcp.core.GcpProjectIdProvider;
import org.springframework.cloud.gcp.core.UserAgentHeaderProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Provides client objects for interfacing with BigQuery.
 *
 * @author Daniel Zou
 */
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(GcpContextAutoConfiguration.class)
@ConditionalOnProperty(value = "spring.cloud.gcp.bigquery.enabled", matchIfMissing = true)
@ConditionalOnClass({ BigQuery.class, BigQueryTemplate.class })
@EnableConfigurationProperties(GcpBigQueryProperties.class)
public class GcpBigQueryAutoConfiguration {

	private final String projectId;

	private final CredentialsProvider credentialsProvider;

	private final String datasetName;

	GcpBigQueryAutoConfiguration(
			GcpBigQueryProperties gcpBigQueryProperties,
			GcpProjectIdProvider projectIdProvider,
			CredentialsProvider credentialsProvider) throws IOException {

		this.projectId = (gcpBigQueryProperties.getProjectId() != null)
				? gcpBigQueryProperties.getProjectId()
				: projectIdProvider.getProjectId();

		this.credentialsProvider = (gcpBigQueryProperties.getCredentials().hasKey()
				? new DefaultCredentialsProvider(gcpBigQueryProperties)
				: credentialsProvider);

		this.datasetName = gcpBigQueryProperties.getDatasetName();
	}

	@Bean
	@ConditionalOnMissingBean
	public BigQuery bigQuery() throws IOException {
		BigQueryOptions bigQueryOptions = BigQueryOptions.newBuilder()
				.setProjectId(this.projectId)
				.setCredentials(this.credentialsProvider.getCredentials())
				.setHeaderProvider(new UserAgentHeaderProvider(GcpBigQueryAutoConfiguration.class))
				.build();
		return bigQueryOptions.getService();
	}

	@Bean
	@ConditionalOnMissingBean
	public BigQueryTemplate bigQueryTemplate(BigQuery bigQuery) {
		return new BigQueryTemplate(bigQuery, this.datasetName);
	}
}

위의 코드는 BigQuery 라이브러리에 포함된 GcpBigQueryAutoConfiguration.java의 코드입니다.

아래와 같이 위의 GcpBigQueryAutoConfiguration 파일에서 BigQuery 객체를 Bean으로 등록하고 있는걸 볼 수 있습니다.

@Bean
@ConditionalOnMissingBean
public BigQuery bigQuery() throws IOException {
}

GcpBigQueryAutoConfiguration 클래스는 스프링 실행시 @Configuration 으로 인해 자동으로 여러 설정을 하는 클래스입니다.

 

지금은 GcpBigQueryAutoConfiguration 파일이 실행되면서 application.properties에 적용된 credentials 파일을 자동으로 읽어 BigQuery 객체를 생성해주는 것을 볼 수 있는데요.

 

이 BigQuery 객체를 각 credentials 파일마다 서로 다른 Bean으로 등록한다면, 여러개의 credentials 파일을 적용한 BigQuery 객체들을 사용할 수 있게 됩니다.

 

3. 여러개의 Credentials 파일 및 BigQuery Bean 등록

위의 application.properties 파일처럼 각 credentials 파일마다의 속성을 정해줍니다.

(여기서 my.gcp.project1.credentials.location 속성은 제가 임의로 정한 속성으로 변경하셔도 됩니다.)

또한 스프링의 main 함수 클래스에서 @SpringBootApplication에 위의 클래스를 제외시켜줍니다.

 

아래의 GCPProviderBeansConfig 클래스에서 각 프로젝트(project1, project2)에 대한 credentials 파일을 읽어 CredentialsProvider를 Bean으로 등록해줍니다.

 

GCPProviderBeansConfig.java 

 

package com.example.gcpmulti;

import com.google.api.gax.core.CredentialsProvider;
import com.google.auth.oauth2.ServiceAccountCredentials;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.FileInputStream;

@Configuration
public class GCPProviderBeansConfig {

    @Value("${my.gcp.project1.credentials.location}")
    private String project1CredentialsLocation;

    @Value("${my.gcp.project2.credentials.location}")
    private String project2CredentialsLocation;

    @Bean(name = "project1CredentialsProvider")
    public CredentialsProvider gcpProject1CredentialsProvider() {
        return () -> ServiceAccountCredentials.fromStream(
                new FileInputStream(project1CredentialsLocation)
        );
    }

    @Bean(name = "project2CredentialsProvider")
    public CredentialsProvider gcpProject2CredentialsProvider() {
        return () -> ServiceAccountCredentials.fromStream(
                new FileInputStream(project2CredentialsLocation)
        );
    }
}

 

GCPProject1Config 클래스에서 project1 에 대한 BigQuery 객체를 "project1BigQuery"라는 이름으로 Bean을 등록해줍니다.

 

GCPProject1Config.java

package com.example.gcpmulti;

import com.google.api.gax.core.CredentialsProvider;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gcp.autoconfigure.bigquery.GcpBigQueryAutoConfiguration;
import org.springframework.cloud.gcp.autoconfigure.bigquery.GcpBigQueryProperties;
import org.springframework.cloud.gcp.autoconfigure.core.GcpContextAutoConfiguration;
import org.springframework.cloud.gcp.bigquery.core.BigQueryTemplate;
import org.springframework.cloud.gcp.core.DefaultCredentialsProvider;
import org.springframework.cloud.gcp.core.GcpProjectIdProvider;
import org.springframework.cloud.gcp.core.UserAgentHeaderProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(GcpContextAutoConfiguration.class)
@ConditionalOnProperty(value = "spring.cloud.gcp.bigquery.enabled", matchIfMissing = true)
@ConditionalOnClass({ BigQuery.class, BigQueryTemplate.class })
@EnableConfigurationProperties(GcpBigQueryProperties.class)
public class GCPProject1Config {

    @Value("${my.gcp.project1.datasetName}")
    private String datasetName;

    private final String projectId;

    private final CredentialsProvider credentialsProvider;

    public GCPProject1Config(GcpBigQueryProperties gcpBigQueryProperties,
                             GcpProjectIdProvider projectIdProvider,
                             @Qualifier("project1CredentialsProvider") CredentialsProvider credentialsProvider) throws IOException {
        this.projectId = (gcpBigQueryProperties.getProjectId() != null)
                ? gcpBigQueryProperties.getProjectId()
                : projectIdProvider.getProjectId();

        this.credentialsProvider = (gcpBigQueryProperties.getCredentials().hasKey()
                ? new DefaultCredentialsProvider(gcpBigQueryProperties)
                : credentialsProvider);
    }

    @Bean(name = "project1BigQuery")
    public BigQuery project1BigQuery() throws IOException {
        BigQueryOptions bigQueryOptions = BigQueryOptions.newBuilder()
                .setProjectId(this.projectId)
                .setCredentials(this.credentialsProvider.getCredentials())
                .setHeaderProvider(new UserAgentHeaderProvider(GcpBigQueryAutoConfiguration.class))
                .build();
        return bigQueryOptions.getService();
    }

    @Bean(name = "project1BigQueryTemplate")
    public BigQueryTemplate project1BigQueryTemplate(@Qualifier("project1BigQuery") BigQuery bigQuery) {
        return new BigQueryTemplate(bigQuery, this.datasetName);
    }
}

 

Project2 역시 GCPProject2Config 클래스에서 프로젝트에 대한 BigQuery 객체를 "project2BigQuery"라는 이름으로 Bean을 등록해줍니다.

 

GCPProject2Config.java

package com.example.gcpmulti;

import com.google.api.gax.core.CredentialsProvider;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gcp.autoconfigure.bigquery.GcpBigQueryAutoConfiguration;
import org.springframework.cloud.gcp.autoconfigure.bigquery.GcpBigQueryProperties;
import org.springframework.cloud.gcp.autoconfigure.core.GcpContextAutoConfiguration;
import org.springframework.cloud.gcp.bigquery.core.BigQueryTemplate;
import org.springframework.cloud.gcp.core.DefaultCredentialsProvider;
import org.springframework.cloud.gcp.core.GcpProjectIdProvider;
import org.springframework.cloud.gcp.core.UserAgentHeaderProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(GcpContextAutoConfiguration.class)
@ConditionalOnProperty(value = "spring.cloud.gcp.bigquery.enabled", matchIfMissing = true)
@ConditionalOnClass({ BigQuery.class, BigQueryTemplate.class })
@EnableConfigurationProperties(GcpBigQueryProperties.class)
public class GCPProject2Config {

    @Value("${my.gcp.project2.datasetName}")
    private String datasetName;

    private final String projectId;

    private final CredentialsProvider credentialsProvider;

    public GCPProject2Config(GcpBigQueryProperties gcpBigQueryProperties,
                             GcpProjectIdProvider projectIdProvider,
                             @Qualifier("project1CredentialsProvider") CredentialsProvider credentialsProvider) throws IOException {
        this.projectId = (gcpBigQueryProperties.getProjectId() != null)
                ? gcpBigQueryProperties.getProjectId()
                : projectIdProvider.getProjectId();

        this.credentialsProvider = (gcpBigQueryProperties.getCredentials().hasKey()
                ? new DefaultCredentialsProvider(gcpBigQueryProperties)
                : credentialsProvider);
    }

    @Bean(name = "project2BigQuery")
    public BigQuery project2BigQuery() throws IOException {
        BigQueryOptions bigQueryOptions = BigQueryOptions.newBuilder()
                .setProjectId(this.projectId)
                .setCredentials(this.credentialsProvider.getCredentials())
                .setHeaderProvider(new UserAgentHeaderProvider(GcpBigQueryAutoConfiguration.class))
                .build();
        return bigQueryOptions.getService();
    }

    @Bean(name = "project2BigQueryTemplate")
    public BigQueryTemplate project2BigQueryTemplate(@Qualifier("project2BigQuery") BigQuery bigQuery) {
        return new BigQueryTemplate(bigQuery, this.datasetName);
    }
}

 

아래는 GCPService 클래스에서 BigQuery를 사용하는 예제입니다.

 

기존에는 스프링에서 "BigQuery bigQuery"로 BigQuery interface에 대한 Bean을 자동으로 주입해주었지만

이제는 BigQuery 구현체의 Bean 객체가 "project1BigQuery"와 "project2BigQuery" 2개로 등록되어 있기때문에 @Qualifier를 이용하여 특정 Bean을 지정합니다.

 

이렇게 특정 GCP credentials 파일들을 지정하여 각 BigQuery 프로젝트를 따로 사용할 수 있습니다.

 

GCPService.java

package com.example.gcpmulti;

import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.FieldValue;
import com.google.cloud.bigquery.FieldValueList;
import com.google.cloud.bigquery.QueryJobConfiguration;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class GCPService {
    private final BigQuery bigquery;

    public GCPService(@Qualifier("project1BigQuery") BigQuery bigquery) {
        this.bigquery = bigquery;
    }

    public void runQuery() throws InterruptedException {
        String query = "SELECT column FROM table;";
        QueryJobConfiguration queryConfig =
                QueryJobConfiguration.newBuilder(query).build();

        // Run the query using the BigQuery object
        for (FieldValueList row : bigquery.query(queryConfig).iterateAll()) {
            for (FieldValue val : row) {
                System.out.println(val);
            }
        }
    }
}

 

4. 결론

스프링 프로젝트에 동시에 여러개의 GCP Credentials 파일을 적용하는 방법을 알아보았습니다.

위의 예제에서는 BigQuery를 사용하였고 다른 GCP 기능들마다 약간씩 적용 방법의 차이는 있을 수 있지만,

전체적으로는 여러개의 credentials 파일이 적용된 Bean을 등록하는 같은 맥락이지 않을까 싶습니다.

 

사실 IAM이나 하나의 credentials에 권한을 추가하는 것도 더 나은 방법일 수 있지만, 내부가 궁금하여 소스코드 한번 들여다 봤습니다.

'백엔드' 카테고리의 다른 글

전략 패턴(Strategy Pattern)  (0) 2021.06.24
Kafka(이벤트 브로커) vs RabbitMQ(메시지 브로커)  (0) 2021.06.24
MySQL의 Prepare Statement  (0) 2021.06.24

댓글