Create a Projects to use These two Services
-
Go to google cloud console https://console.cloud.google.com/
-
Click
-
Create a project
-
Choose the service
In our case we need
-
Gmail API
-
Pub/Sub API
And then
enable API, try now, ..., etc
(each service has the button placed in different positions). -
Create OAuth Detail
-
Then we can click
to control the access right (called scope) to the service for users authenticated through oauth.
-
Step 1. Most of the fields are optional, just fill in
App name
andUser support email
.then click save and continue at the bottom.
-
Step 2. We define access right for oauth-authenticated users. Click
Add or remove scopes
:in our case we need:
-
Step 3. Add users who is available to using the apis from this project
james.lee now can use
pubsub
andgmailapi
with defined rights in the scopes. -
Since we are the only users, we can keep staying at test state.
Create Credentials
-
Save and continue, we create credentials for authentication.
-
Choose Desktop app
-
Fill in the blanks, then
create
. -
The resulting
credential.json
is for api which useoauth-v2
authentication.
Another Version of Credential (can skip and revisit when needed, pubsub library may need it)
-
Since we are not going to use pubsub's
pulling
, we just receivepush
notification by webhook (let google send post request to us), we don't need any package for pubsub in our project which requiresoauth-v1
credential. -
In the future if pulling is needed, we can get v1 credential in the folloing way:
- manage server account
- manage keys
- add key download json:
Configure Pubsub API
Create Topic
-
Go to pub/sub api page by using the search bar again
-
Create a topic with default option
Create Subscription
-
Create subscription and selected our newly created topic
Create Publisher for the Topic
-
We next create publisher that monitor our mail box and push message to the topic. We following documentation about push notifications in gmail api.
To do this, you need to grant publish privileges to gmail-api-push@system.gserviceaccount.com. You can do this using the Cloud Pub/Sub Developer Console permissions interface following the resource-level access control instructions.
-
Select Topic
-
Click Add Principal at the right column
-
Fill in the email address, then assign publisher right
-
Create Subscriber for the Topic
-
Next we grant
receiver
right to our google users:-
select a topic in subscriptions
-
Add principal at the right column, then fill in:
-
Use the Credential in the Program
-
Place your credential at
name it
credential-v2-gmail.json
, this is configured inGoogleConfig.java
.We may move the string to properties file, but 3 environments can share the same crendential.
-
The actual authentication will be done when we run the springboot program, we will be asked to sigin in through a given link in the terminal.
-
Once authentication is done, a file will be created in
token-local/
and we will not be asked to login again when that file exists. -
In properties file we have configured
GoogleConfig
to usetoken-uat/
,token-prd/
in different environment -
To avoid the package prompting login action in
uat
andprd
, we may create these addional two folders sign-in copy the generated file into the correct folder before deployment.
Code Implementation to Use Credential and Start Webhook in Java
The following is essentially a modification of code presented in documentation.
mport com.google.api.client.auth.oauth2.Credential; import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.util.store.FileDataStoreFactory; import com.google.api.services.gmail.Gmail; import com.google.api.services.gmail.GmailScopes; import com.google.api.services.gmail.model.WatchRequest; import com.google.api.services.gmail.model.WatchResponse; import com.wonderbricks.web.service.impl.GmailServiceImpl; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GoogleConfig { @Value("${google.token}") private String TOKENS_DIRECTORY_PATH; @Value("${google.credential}") private String CREDENTIAL_FILE_PATH; @Value("${google.app}") private String APPLICATION_NAME; @Value("${google.pub.topic}") private String PUBLISHER_TOPIC_NAME; private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); private static final List<String> SCOPES = Collections.singletonList(GmailScopes.MAIL_GOOGLE_COM); @Bean Gmail getGmail() throws GeneralSecurityException, IOException { final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); Gmail service = new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) .setApplicationName(APPLICATION_NAME) .build(); return service; } private Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException { InputStream in = GmailServiceImpl.class.getResourceAsStream(CREDENTIAL_FILE_PATH); if (in == null) { throw new FileNotFoundException("Resource not found: " + CREDENTIAL_FILE_PATH); } GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) .setAccessType("offline") .build(); LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build(); Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user"); return credential; } public void startWebHook(Gmail gmail) throws IOException, InterruptedException { WatchRequest request = new WatchRequest(); request.setLabelIds(Arrays.asList("INBOX")); request.setTopicName(PUBLISHER_TOPIC_NAME); request.setLabelFilterAction("INCLUDE"); WatchResponse res = gmail.users().watch("me", request).execute(); Long expiredAfter = res.getExpiration(); // millis CompletableFuture.delayedExecutor(expiredAfter, TimeUnit.MILLISECONDS).execute(() -> { try { startWebHook(gmail); } catch (IOException | InterruptedException e) { e.printStackTrace(); } }); } // Run after application context has been created @Bean CommandLineRunner initWebhook(Gmail gmail) { return args -> { var logger = LoggerFactory.getLogger(GoogleConfig.class); logger.info("Start Watching Gmail Account"); startWebHook(gmail); }; } }