@@ -93,6 +93,8 @@ public class ServiceAccountCredentials extends GoogleCredentials
9393 private static final long serialVersionUID = 7807543542681217978L ;
9494 private static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer" ;
9595 private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. " ;
96+ private static final int TWELVE_HOURS_IN_SECONDS = 43200 ;
97+ private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600 ;
9698
9799 private final String clientId ;
98100 private final String clientEmail ;
@@ -104,6 +106,7 @@ public class ServiceAccountCredentials extends GoogleCredentials
104106 private final URI tokenServerUri ;
105107 private final Collection <String > scopes ;
106108 private final String quotaProjectId ;
109+ private final int lifetime ;
107110
108111 private transient HttpTransportFactory transportFactory ;
109112
@@ -123,6 +126,10 @@ public class ServiceAccountCredentials extends GoogleCredentials
123126 * authority to the service account.
124127 * @param projectId the project used for billing
125128 * @param quotaProjectId The project used for quota and billing purposes. May be null.
129+ * @param lifetime number of seconds the access token should be valid for. The value should be at
130+ * most 43200 (12 hours). If the token is used for calling a Google API, then the value should
131+ * be at most 3600 (1 hour). If the given value is 0, then the default value 3600 will be used
132+ * when creating the credentials.
126133 */
127134 ServiceAccountCredentials (
128135 String clientId ,
@@ -134,7 +141,8 @@ public class ServiceAccountCredentials extends GoogleCredentials
134141 URI tokenServerUri ,
135142 String serviceAccountUser ,
136143 String projectId ,
137- String quotaProjectId ) {
144+ String quotaProjectId ,
145+ int lifetime ) {
138146 this .clientId = clientId ;
139147 this .clientEmail = Preconditions .checkNotNull (clientEmail );
140148 this .privateKey = Preconditions .checkNotNull (privateKey );
@@ -149,6 +157,10 @@ public class ServiceAccountCredentials extends GoogleCredentials
149157 this .serviceAccountUser = serviceAccountUser ;
150158 this .projectId = projectId ;
151159 this .quotaProjectId = quotaProjectId ;
160+ if (lifetime > TWELVE_HOURS_IN_SECONDS ) {
161+ throw new IllegalStateException ("lifetime must be less than or equal to 43200" );
162+ }
163+ this .lifetime = lifetime ;
152164 }
153165
154166 /**
@@ -325,7 +337,8 @@ static ServiceAccountCredentials fromPkcs8(
325337 tokenServerUri ,
326338 serviceAccountUser ,
327339 projectId ,
328- quotaProject );
340+ quotaProject ,
341+ DEFAULT_LIFETIME_IN_SECONDS );
329342 }
330343
331344 /** Helper to convert from a PKCS#8 String to an RSA private key */
@@ -513,7 +526,21 @@ public GoogleCredentials createScoped(Collection<String> newScopes) {
513526 tokenServerUri ,
514527 serviceAccountUser ,
515528 projectId ,
516- quotaProjectId );
529+ quotaProjectId ,
530+ lifetime );
531+ }
532+
533+ /**
534+ * Clones the service account with a new lifetime value.
535+ *
536+ * @param lifetime life time value in seconds. The value should be at most 43200 (12 hours). If
537+ * the token is used for calling a Google API, then the value should be at most 3600 (1 hour).
538+ * If the given value is 0, then the default value 3600 will be used when creating the
539+ * credentials.
540+ * @return the cloned service account credentials with the given custom life time
541+ */
542+ public ServiceAccountCredentials createWithCustomLifetime (int lifetime ) {
543+ return this .toBuilder ().setLifetime (lifetime ).build ();
517544 }
518545
519546 @ Override
@@ -528,7 +555,8 @@ public GoogleCredentials createDelegated(String user) {
528555 tokenServerUri ,
529556 user ,
530557 projectId ,
531- quotaProjectId );
558+ quotaProjectId ,
559+ lifetime );
532560 }
533561
534562 public final String getClientId () {
@@ -563,6 +591,11 @@ public final URI getTokenServerUri() {
563591 return tokenServerUri ;
564592 }
565593
594+ @ VisibleForTesting
595+ int getLifetime () {
596+ return lifetime ;
597+ }
598+
566599 @ Override
567600 public String getAccount () {
568601 return getClientEmail ();
@@ -618,7 +651,8 @@ public int hashCode() {
618651 transportFactoryClassName ,
619652 tokenServerUri ,
620653 scopes ,
621- quotaProjectId );
654+ quotaProjectId ,
655+ lifetime );
622656 }
623657
624658 @ Override
@@ -632,6 +666,7 @@ public String toString() {
632666 .add ("scopes" , scopes )
633667 .add ("serviceAccountUser" , serviceAccountUser )
634668 .add ("quotaProjectId" , quotaProjectId )
669+ .add ("lifetime" , lifetime )
635670 .toString ();
636671 }
637672
@@ -648,7 +683,8 @@ public boolean equals(Object obj) {
648683 && Objects .equals (this .transportFactoryClassName , other .transportFactoryClassName )
649684 && Objects .equals (this .tokenServerUri , other .tokenServerUri )
650685 && Objects .equals (this .scopes , other .scopes )
651- && Objects .equals (this .quotaProjectId , other .quotaProjectId );
686+ && Objects .equals (this .quotaProjectId , other .quotaProjectId )
687+ && Objects .equals (this .lifetime , other .lifetime );
652688 }
653689
654690 String createAssertion (JsonFactory jsonFactory , long currentTime , String audience )
@@ -661,7 +697,7 @@ String createAssertion(JsonFactory jsonFactory, long currentTime, String audienc
661697 JsonWebToken .Payload payload = new JsonWebToken .Payload ();
662698 payload .setIssuer (clientEmail );
663699 payload .setIssuedAtTimeSeconds (currentTime / 1000 );
664- payload .setExpirationTimeSeconds (currentTime / 1000 + 3600 );
700+ payload .setExpirationTimeSeconds (currentTime / 1000 + this . lifetime );
665701 payload .setSubject (serviceAccountUser );
666702 payload .put ("scope" , Joiner .on (' ' ).join (scopes ));
667703
@@ -693,7 +729,7 @@ String createAssertionForIdToken(
693729 JsonWebToken .Payload payload = new JsonWebToken .Payload ();
694730 payload .setIssuer (clientEmail );
695731 payload .setIssuedAtTimeSeconds (currentTime / 1000 );
696- payload .setExpirationTimeSeconds (currentTime / 1000 + 3600 );
732+ payload .setExpirationTimeSeconds (currentTime / 1000 + this . lifetime );
697733 payload .setSubject (serviceAccountUser );
698734
699735 if (audience == null ) {
@@ -746,6 +782,7 @@ public static class Builder extends GoogleCredentials.Builder {
746782 private Collection <String > scopes ;
747783 private HttpTransportFactory transportFactory ;
748784 private String quotaProjectId ;
785+ private int lifetime = DEFAULT_LIFETIME_IN_SECONDS ;
749786
750787 protected Builder () {}
751788
@@ -760,6 +797,7 @@ protected Builder(ServiceAccountCredentials credentials) {
760797 this .serviceAccountUser = credentials .serviceAccountUser ;
761798 this .projectId = credentials .projectId ;
762799 this .quotaProjectId = credentials .quotaProjectId ;
800+ this .lifetime = credentials .lifetime ;
763801 }
764802
765803 public Builder setClientId (String clientId ) {
@@ -812,6 +850,11 @@ public Builder setQuotaProjectId(String quotaProjectId) {
812850 return this ;
813851 }
814852
853+ public Builder setLifetime (int lifetime ) {
854+ this .lifetime = lifetime == 0 ? DEFAULT_LIFETIME_IN_SECONDS : lifetime ;
855+ return this ;
856+ }
857+
815858 public String getClientId () {
816859 return clientId ;
817860 }
@@ -852,6 +895,10 @@ public String getQuotaProjectId() {
852895 return quotaProjectId ;
853896 }
854897
898+ public int getLifetime () {
899+ return lifetime ;
900+ }
901+
855902 public ServiceAccountCredentials build () {
856903 return new ServiceAccountCredentials (
857904 clientId ,
@@ -863,7 +910,8 @@ public ServiceAccountCredentials build() {
863910 tokenServerUri ,
864911 serviceAccountUser ,
865912 projectId ,
866- quotaProjectId );
913+ quotaProjectId ,
914+ lifetime );
867915 }
868916 }
869917}
0 commit comments