Skip to content

Instantly share code, notes, and snippets.

@joaogcs
Last active April 28, 2022 01:15
Show Gist options
  • Save joaogcs/310ef8fc2b8aeff8c421a7347b39351d to your computer and use it in GitHub Desktop.
Save joaogcs/310ef8fc2b8aeff8c421a7347b39351d to your computer and use it in GitHub Desktop.
Google API with Spring and Kotlin

Google API with Spring and Kotlin

Guideline to use Google API dependencies with Spring and Kotlin

Table of Contents

What is this guide for?

This guideline shows how to configure and use Google APIs with Spring and Kotlin. Google Services used as samples are Sheets and IAM.

How to create Google Service Account

In order to go further make sure you have a Google Service Account with at least one generated Key.

Reference: https://cloud.google.com/iam/docs/creating-managing-service-account-keys

Configuration

Configure Google API Maven Dependencies

Google has a bunch of dependencies to all kind of services on Google Cloud Platform.

Reference: https://github.com/googleapis/google-api-java-client-services#supported-google-apis

⚠️ WARN

Every Google API Service needs to enabled on GCP (Google Cloud Platform).

For example Sheets API or Admin SDK API

Reference: https://console.cloud.google.com/projectselector2/apis/dashboard?organizationId=0&supportedpurview=project

Add the below dependencies to your pom.xml

<!-- Required -> used to configure Google Service Account Credentials -->
<dependency>
    <groupId>com.google.oauth-client</groupId>
    <artifactId>google-oauth-client-jetty</artifactId>
    <version>${google-api-client.version}</version>
</dependency>
<!-- Required -> used to configure Google Service Account Credentials -->
<dependency>
    <groupId>com.google.auth</groupId>
    <artifactId>google-auth-library-oauth2-http</artifactId>
    <version>${google-auth-library-oauth2-http.version}</version>
</dependency>
<!-- Required -> used to convert google json responses -->
<dependency>
    <groupId>com.google.http-client</groupId>
    <artifactId>google-http-client-jackson2</artifactId>
    <version>${google-http-client-jackson2.version}</version>
</dependency>
<!-- Optional -> used to access Google Sheets services -->
<dependency>
    <groupId>com.google.apis</groupId>
    <artifactId>google-api-services-sheets</artifactId>
    <version>${google-api-services-sheets.version}</version>
</dependency>
<!-- Optional -> used to access Google IAM services -->
<dependency>
    <groupId>com.google.apis</groupId>
    <artifactId>google-api-services-admin-directory</artifactId>
    <version>directory_v1-rev118-1.25.0</version>
</dependency>

Configure Google Account Service in Spring Boot

Create a Configuration class with the following structure.

⚠️ WARN

Read all comments from code below !

import com.google.api.services.admin.directory.DirectoryScopes
import com.google.auth.http.HttpCredentialsAdapter
import com.google.auth.oauth2.ServiceAccountCredentials
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

/*
* Handles Google API Configuration of all Google Services.
*/
@Configuration("GoogleConfig")
class GoogleConfig {

    /*
    * This Bean manages Google Credentials for every Google Service you will use on this component
    */
    @Bean
    fun getCredentials(): HttpCredentialsAdapter {
        val googleCredentials = ServiceAccountCredentials.fromPkcs8(
            /*
            * Credentials values from generated credentials.json.
            * Replace all values here with the values of json file.
            * Reference: https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-list-console
            * */
            "client_id",
            "client_email",
            "private_key",
            "private_key_id",

            /*
            * List of all Google Scopes the Service Account will need.
            * The list is String format and all Google Dependencies has it's own SCOPES.
            * Reference: https://developers.google.com/identity/protocols/oauth2/scopes
            * */
            emptyList(),
        )
        return HttpCredentialsAdapter(googleCredentials)
    }
}

Configure new Google Service like Sheets or IAM

Each Google Service has it's own dependency and it needs to be configured individually.

To add a new Service like Sheets and IAM:

  • create a Bean for each of then on GoogleConfig class like below.
    • make sure you put your application name, is a free text field
  • add scopes for each service at getCredentials Bean

ℹ️ INFO

All Google Services are listed at https://github.com/googleapis/google-api-java-client-services#supported-google-apis

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.services.admin.directory.Directory
import com.google.api.services.admin.directory.DirectoryScopes
import com.google.api.services.sheets.v4.Sheets
import com.google.auth.http.HttpCredentialsAdapter
import com.google.auth.oauth2.ServiceAccountCredentials
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

/*
* Handles Google API Configuration of all Google Services.
*/
@Configuration("GoogleConfig")
class GoogleConfig {

    /*
    * This Bean manages Google Credentials for every Google Service you will use on this component
    */
    @Bean
    fun getCredentials(): HttpCredentialsAdapter {
        val googleCredentials = ServiceAccountCredentials.fromPkcs8(
            /*
            * Credentials values from generated credentials.json.
            * Replace all values here with the values of json file.
            * Reference: https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-list-console
            * */
            "client_id",
            "client_email",
            "private_key",
            "private_key_id",

            /*
            * List of all Google Scopes the Service Account will need.
            * The list is String format and all Google Dependencies has it's own SCOPES.
            * The first element of the example below is String format and the second is getting from Dependency SCOPE.
            * Reference: https://developers.google.com/identity/protocols/oauth2/scopes
            * */
            listOf(
                "https://www.googleapis.com/auth/spreadsheets.readonly", DirectoryScopes.ADMIN_DIRECTORY_GROUP
            ),
        )
        return HttpCredentialsAdapter(googleCredentials)
    }

    /*
    * Configuration for Google Sheets service
    */
    @Bean
    fun getGoogleSheetsService(credentials: HttpCredentialsAdapter): Sheets {
        return Sheets.Builder(
            GoogleNetHttpTransport.newTrustedTransport(), JacksonFactory.getDefaultInstance(), credentials
        ).setApplicationName("your application name").build()
    }

    /*
    * Configuration for Google IAM service
    */
    @Bean
    fun getDirectoryService(credentials: HttpCredentialsAdapter): Directory {
        return Directory.Builder(
            GoogleNetHttpTransport.newTrustedTransport(), JacksonFactory.getDefaultInstance(), credentials
        ).setApplicationName("your application name").build()
    }
}

How to use Google Services

At your External Interfaces you can use all your configured Google Services as you wish.

No need to handle Google Service Account authentication.

Every Google Service has it's own methods, types and exceptions. So be sure to read the documentation of the service you want to use.

Below is a sample implementation to read a Google Sheet range.

import com.amaro.secondpartner.sample.usecases.ports.StoragePort
import com.google.api.services.sheets.v4.Sheets
import org.springframework.stereotype.Component

@Component
class SheetsStorage(private val sheets: Sheets) : StoragePort {

    override fun getSheetRange(spreadsheetId: String): MutableList<MutableList<Any>>? {
        /* 
        * Read Google Sheets API documentation to know how it works
        */
        return sheets.spreadsheets().values()
            .get(spreadsheetId, "A1:B2")
            .setValueRenderOption("FORMATTED_VALUE")
            .execute()
            .getValues()
    }
}

Below is a sample use case without implementation to list 10 IAM groups.

import com.google.api.services.admin.directory.Directory
import com.google.api.services.admin.directory.model.Group
import com.google.api.services.admin.directory.model.Groups
import com.google.api.services.admin.directory.model.User
import com.google.api.services.admin.directory.model.Users
import org.springframework.stereotype.Service

@Service
class ReadUsersUseCase(
    val directory: Directory
) {

    fun allFromGCP(): List<User> {
        val result: Users = directory.users().list()
            .setMaxResults(10)
            .setOrderBy("email")
            .execute()
        return result.users
    }

    fun allUserGroupsFromGCP(): List<Group> {
        val result: Groups = directory.groups().list()
            .setMaxResults(10)
            .execute()
        return result.groups
    }
}

FAQ - Common errors

Error - Google Services returns 403 Forbidden

If your code throws something like the below message.

Make sure to enable Google Service API you are trying to access on GCP.

✔️ Solution

Visit the error message link in order to enable the API in GCP.

You can also visit https://console.cloud.google.com/projectselector2/apis/dashboard?organizationId=0&supportedpurview=project to search for Google APIs.

com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
GET https://www.googleapis.com/admin/directory/v1/groups?maxResults=10
{
  "code" : 403,
  "details" : [ {
    "@type" : "type.googleapis.com/google.rpc.Help"
  }, {
    "@type" : "type.googleapis.com/google.rpc.ErrorInfo"
  } ],
  "errors" : [ {
    "domain" : "usageLimits",
    "message" : "Admin SDK API has not been used in project 323312394567 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/admin.googleapis.com/overview?project=323312394567 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
    "reason" : "accessNotConfigured",
    "extendedHelp" : "https://console.developers.google.com"
  } ],
  "message" : "Admin SDK API has not been used in project 323312394567 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/admin.googleapis.com/overview?project=323312394567 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
  "status" : "PERMISSION_DENIED"
}

Error - java.io.IOException: Invalid PKCS#8 data

The error message java.io.IOException: Invalid PKCS#8 data. is caused by wrong Google Service Account Private Key value.

✔️ Solution for Production and Dev Environment

In production environment the GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY environment variable is set by Kubernetes. Contact Devops team to update it for you.

Example of Private Key format with Spring configuration

Break lines must be replaced by \n

src/main/resources/application.yml

# FAKE PRIVATE KEY VALUE
google:
  service-account:
    private_key: "-----BEGIN PRIVATE KEY-----\nMIIEdsuhINidHidhddsaduhiDHiuhJDSOAIjoidjadASDhbdsbiuadBHUtpIbNBC\nIASjdosiadjaoiDsa98dsjvCZ5IZbVwYgI/ASdhudshaiURbmsOqBEk3jnrC1Lqe\nyEugQeDNf+OU+AO6dR1OLO7S5XFLhcTGPsaxBTPTi7bDypJFmb9mdAgh9zATegJP\nQC8UHrAGD5x6ahkzYctEtLtByBPbFHlzGyCMrpS3PBQHjaOju5qdDAyhVUMtKAAV\nyrz5JKdoozZp/MuQVXgSZnBmbUu+toXLU6ZE6CJ+Ke/+gjIIYE+A788HgQoUh5zf\nBoEo0epcV7HUlLYZXm/D99G6sp1Atd6jMyOAduKz9i1mC8JlhC0E1pwTxMryko0a\nEJqwIzIxAgMBAAECggEAAdSXBXeIdr+gEfRyfm7ZbyoMY+PLpGxRwgmrHiXFhpH2\nVxv3LkDORADqOanp17aUnSjdaCP3og/Gc+OScOoywXKkfzmUezSGNGG2F6AXoK78\nWKPi1JJQKPBSo/gFPlUzS68k5RxTRsOmwvb4Ucrk4EHnnIO5i098e8ksG8Ozy/u6\nx2rrdFyySlPigqxVFp0aCUBTD/dKLeJc0f1PYxxEseVlMwv4WPratO3VF7vSzbwZ\nqt0s+xQxfNEm2guOahpVLm2XMzkkSEQg5fY6ZGnmSDVclI8FLgmCpBMAiPC1q4hm\nGKDCcd9QsARYyYIcKS2Wi7f1F5LcSAIdC6GheIu+MQKBgQD64fjnNYA6jXOyvr0F\naBnujL8zCFtbfS1394S9jEla8a9N0W7L5bqz9FOiPVkZmO2OGVtXtbEjo7V5dHLs\no7jOy7g4rnagYhZyc74Ft96PNm/5f+R8+Hqo4YmDF0wq3bAWk7q3/vfy7bmQ1gtG\nX+6FjoKNBX+W+TOkmIRZxs3NrQKBgQDvO7qNwSyxCoIU9KwL/39TKaLqdpxwMHVC\nZZaHTazdpMVUnKOmIhTV529azWT61R0nk9yzKg/afb3X1mfD+rY0HGX4VojovhPb\nJ9e7VB6JSnC/isGy2H9fW7A2UWQj4ZIO7S/WF7axZqhHlGO26oadosW6PChBtyFX\nX1qrjyr/FQKBgQDB5rzVFgiROJpfYAP/lcHVGXr9GkxhnZHy0p8JUM+xNnJjFqcn\nd9qhEwlAr2GZ4xRXa0mptIfAH9s2j8XzF2bSjalIRa7xrl3i/4myKbYdwdkxYKNb\n40AKHqaFZxA7YTOjf3Ikmy9P1mRBiO0V6zcSil5kWAeQy/IaGLbSHFEb0QKBgEC3\nlrr8VBu8rP4ARn6kaoxTyifFWIKdCUuh8bu+jpITHfLKwRaTR7Gp/xzATZ3xgwaO\n4HhzW4CO3YmDDeUdcKbeO9OXjUfxC6wQtjKOCgi345JddhLssGBajGvVTtLKFdoF\n3hf7qEeLbuCWSvdTsID6ZKUB2x4T7WgWeo/IligVAoGBAOh9uLr9c3awcDiB36zs\nDpxJrkF5KDAhEdPNsa6ZqKhN8Q2YNe+mMV184Q58VtPmjuKA/XVhZ0aw8Wr1vRuk\nGVj4BwX0bP7hN0l7nGoBrEDCvRBTajo96T1KrfEYZyr0KHtJ40jJqHryXq/1mh53\nSYgZUc2+rpgkq3G2WV/yNvER\n-----END PRIVATE KEY-----"

✔️ Solution for environment variable on Docker

When running locally with Docker the GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY environment variable is set by local.env file.

Example of Private Key format with Docker

Break lines must exist in file.

local.env

# FAKE PRIVATE KEY VALUE
GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIIEdsuhINidHidhddsaduhiDHiuhJDSOAIjoidjadASDhbdsbiuadBHUtpIbNBC
IASjdosiadjaoiDsa98dsjvCZ5IZbVwYgI/ASdhudshaiURbmsOqBEk3jnrC1Lqe
yEugQeDNf+OU+AO6dR1OLO7S5XFLhcTGPsaxBTPTi7bDypJFmb9mdAgh9zATegJP
QC8UHrAGD5x6ahkzYctEtLtByBPbFHlzGyCMrpS3PBQHjaOju5qdDAyhVUMtKAAV
yrz5JKdoozZp/MuQVXgSZnBmbUu+toXLU6ZE6CJ+Ke/+gjIIYE+A788HgQoUh5zf
BoEo0epcV7HUlLYZXm/D99G6sp1Atd6jMyOAduKz9i1mC8JlhC0E1pwTxMryko0a
EJqwIzIxAgMBAAECggEAAdSXBXeIdr+gEfRyfm7ZbyoMY+PLpGxRwgmrHiXFhpH2
Vxv3LkDORADqOanp17aUnSjdaCP3og/Gc+OScOoywXKkfzmUezSGNGG2F6AXoK78
WKPi1JJQKPBSo/gFPlUzS68k5RxTRsOmwvb4Ucrk4EHnnIO5i098e8ksG8Ozy/u6
x2rrdFyySlPigqxVFp0aCUBTD/dKLeJc0f1PYxxEseVlMwv4WPratO3VF7vSzbwZ
qt0s+xQxfNEm2guOahpVLm2XMzkkSEQg5fY6ZGnmSDVclI8FLgmCpBMAiPC1q4hm
GKDCcd9QsARYyYIcKS2Wi7f1F5LcSAIdC6GheIu+MQKBgQD64fjnNYA6jXOyvr0F
aBnujL8zCFtbfS1394S9jEla8a9N0W7L5bqz9FOiPVkZmO2OGVtXtbEjo7V5dHLs
o7jOy7g4rnagYhZyc74Ft96PNm/5f+R8+Hqo4YmDF0wq3bAWk7q3/vfy7bmQ1gtG
X+6FjoKNBX+W+TOkmIRZxs3NrQKBgQDvO7qNwSyxCoIU9KwL/39TKaLqdpxwMHVC
ZZaHTazdpMVUnKOmIhTV529azWT61R0nk9yzKg/afb3X1mfD+rY0HGX4VojovhPb
J9e7VB6JSnC/isGy2H9fW7A2UWQj4ZIO7S/WF7axZqhHlGO26oadosW6PChBtyFX
X1qrjyr/FQKBgQDB5rzVFgiROJpfYAP/lcHVGXr9GkxhnZHy0p8JUM+xNnJjFqcn
d9qhEwlAr2GZ4xRXa0mptIfAH9s2j8XzF2bSjalIRa7xrl3i/4myKbYdwdkxYKNb
40AKHqaFZxA7YTOjf3Ikmy9P1mRBiO0V6zcSil5kWAeQy/IaGLbSHFEb0QKBgEC3
lrr8VBu8rP4ARn6kaoxTyifFWIKdCUuh8bu+jpITHfLKwRaTR7Gp/xzATZ3xgwaO
4HhzW4CO3YmDDeUdcKbeO9OXjUfxC6wQtjKOCgi345JddhLssGBajGvVTtLKFdoF
3hf7qEeLbuCWSvdTsID6ZKUB2x4T7WgWeo/IligVAoGBAOh9uLr9c3awcDiB36zs
DpxJrkF5KDAhEdPNsa6ZqKhN8Q2YNe+mMV184Q58VtPmjuKA/XVhZ0aw8Wr1vRuk
GVj4BwX0bP7hN0l7nGoBrEDCvRBTajo96T1KrfEYZyr0KHtJ40jJqHryXq/1mh53
SYgZUc2+rpgkq3G2WV/yNvER
-----END PRIVATE KEY-----"

References

https://developers.google.com/learn/pathways/springboot-google-cloud

https://cloud.google.com/docs/authentication/getting-started

https://cloud.google.com/docs/authentication/production

https://mvnrepository.com/artifact/com.google.apis/google-api-services-iam

https://cloud.google.com/iam/docs/write-policy-client-libraries

https://cloud.google.com/resource-manager/docs/libraries

https://stackoverflow.com/questions/18912319/retrieve-all-groups-for-a-member-using-google-admin-java-sdk

https://github.com/googleapis/google-api-java-client-services#supported-google-apis

https://github.com/googleapis/google-api-java-client/

https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-list-console

https://mvnrepository.com/artifact/com.google.apis/google-api-services-admin-directory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment