@@ -110,7 +110,7 @@ public class ServiceAccountCredentials extends GoogleCredentials
110110 private final Collection <String > defaultScopes ;
111111 private final String quotaProjectId ;
112112 private final int lifetime ;
113- private final boolean useJWTAccessWithScope ;
113+ private final boolean useJwtAccessWithScope ;
114114
115115 private transient HttpTransportFactory transportFactory ;
116116
@@ -134,7 +134,7 @@ public class ServiceAccountCredentials extends GoogleCredentials
134134 * most 43200 (12 hours). If the token is used for calling a Google API, then the value should
135135 * be at most 3600 (1 hour). If the given value is 0, then the default value 3600 will be used
136136 * when creating the credentials.
137- * @param useJWTAccessWithScope whether self signed JWT with scopes should be always used.
137+ * @param useJwtAccessWithScope whether self signed JWT with scopes should be always used.
138138 */
139139 ServiceAccountCredentials (
140140 String clientId ,
@@ -149,7 +149,7 @@ public class ServiceAccountCredentials extends GoogleCredentials
149149 String projectId ,
150150 String quotaProjectId ,
151151 int lifetime ,
152- boolean useJWTAccessWithScope ) {
152+ boolean useJwtAccessWithScope ) {
153153 this .clientId = clientId ;
154154 this .clientEmail = Preconditions .checkNotNull (clientEmail );
155155 this .privateKey = Preconditions .checkNotNull (privateKey );
@@ -170,7 +170,7 @@ public class ServiceAccountCredentials extends GoogleCredentials
170170 throw new IllegalStateException ("lifetime must be less than or equal to 43200" );
171171 }
172172 this .lifetime = lifetime ;
173- this .useJWTAccessWithScope = useJWTAccessWithScope ;
173+ this .useJwtAccessWithScope = useJwtAccessWithScope ;
174174 }
175175
176176 /**
@@ -692,7 +692,7 @@ public GoogleCredentials createScoped(
692692 projectId ,
693693 quotaProjectId ,
694694 lifetime ,
695- useJWTAccessWithScope );
695+ useJwtAccessWithScope );
696696 }
697697
698698 /**
@@ -709,13 +709,13 @@ public ServiceAccountCredentials createWithCustomLifetime(int lifetime) {
709709 }
710710
711711 /**
712- * Clones the service account with a new useJWTAccessWithScope value.
712+ * Clones the service account with a new useJwtAccessWithScope value.
713713 *
714- * @param useJWTAccessWithScope whether self signed JWT with scopes should be used
715- * @return the cloned service account credentials with the given useJWTAccessWithScope
714+ * @param useJwtAccessWithScope whether self signed JWT with scopes should be used
715+ * @return the cloned service account credentials with the given useJwtAccessWithScope
716716 */
717- public ServiceAccountCredentials createWithUseJWTAccessWithScope (boolean useJWTAccessWithScope ) {
718- return this .toBuilder ().setUseJWTAccessWithScope ( useJWTAccessWithScope ).build ();
717+ public ServiceAccountCredentials createWithUseJwtAccessWithScope (boolean useJwtAccessWithScope ) {
718+ return this .toBuilder ().setUseJwtAccessWithScope ( useJwtAccessWithScope ).build ();
719719 }
720720
721721 @ Override
@@ -733,7 +733,7 @@ public GoogleCredentials createDelegated(String user) {
733733 projectId ,
734734 quotaProjectId ,
735735 lifetime ,
736- useJWTAccessWithScope );
736+ useJwtAccessWithScope );
737737 }
738738
739739 public final String getClientId () {
@@ -781,8 +781,8 @@ int getLifetime() {
781781 return lifetime ;
782782 }
783783
784- public boolean getUseJWTAccessWithScope () {
785- return useJWTAccessWithScope ;
784+ public boolean getUseJwtAccessWithScope () {
785+ return useJwtAccessWithScope ;
786786 }
787787
788788 @ Override
@@ -843,7 +843,7 @@ public int hashCode() {
843843 defaultScopes ,
844844 quotaProjectId ,
845845 lifetime ,
846- useJWTAccessWithScope );
846+ useJwtAccessWithScope );
847847 }
848848
849849 @ Override
@@ -859,7 +859,7 @@ public String toString() {
859859 .add ("serviceAccountUser" , serviceAccountUser )
860860 .add ("quotaProjectId" , quotaProjectId )
861861 .add ("lifetime" , lifetime )
862- .add ("useJWTAccessWithScope " , useJWTAccessWithScope )
862+ .add ("useJwtAccessWithScope " , useJwtAccessWithScope )
863863 .toString ();
864864 }
865865
@@ -879,7 +879,7 @@ public boolean equals(Object obj) {
879879 && Objects .equals (this .defaultScopes , other .defaultScopes )
880880 && Objects .equals (this .quotaProjectId , other .quotaProjectId )
881881 && Objects .equals (this .lifetime , other .lifetime )
882- && Objects .equals (this .useJWTAccessWithScope , other .useJWTAccessWithScope );
882+ && Objects .equals (this .useJwtAccessWithScope , other .useJwtAccessWithScope );
883883 }
884884
885885 String createAssertion (JsonFactory jsonFactory , long currentTime , String audience )
@@ -949,19 +949,41 @@ String createAssertionForIdToken(
949949 }
950950 }
951951
952+ /**
953+ * Self signed JWT uses uri as audience, which should have the "https://{host}/" format. For
954+ * instance, if the uri is "https://compute.googleapis.com/compute/v1/projects/", then this
955+ * function returns "https://compute.googleapis.com/".
956+ */
957+ @ VisibleForTesting
958+ static URI getUriForSelfSignedJWT (URI uri ) {
959+ if (uri == null || uri .getScheme () == null || uri .getHost () == null ) {
960+ return uri ;
961+ }
962+ try {
963+ return new URI (uri .getScheme (), uri .getHost (), "/" , null );
964+ } catch (URISyntaxException unused ) {
965+ return uri ;
966+ }
967+ }
968+
952969 @ VisibleForTesting
953970 JwtCredentials createSelfSignedJwtCredentials (final URI uri ) {
954971 // Create a JwtCredentials for self signed JWT. See https://google.aip.dev/auth/4111.
955972 JwtClaims .Builder claimsBuilder =
956973 JwtClaims .newBuilder ().setIssuer (clientEmail ).setSubject (clientEmail );
957- if (!scopes .isEmpty ()) {
958- claimsBuilder .setAdditionalClaims (
959- Collections .singletonMap ("scope" , Joiner .on (' ' ).join (scopes )));
960- } else if (uri != null ) {
961- claimsBuilder .setAudience (uri .toString ());
974+
975+ if (uri == null ) {
976+ // If uri is null, use scopes.
977+ String scopeClaim = "" ;
978+ if (!scopes .isEmpty ()) {
979+ scopeClaim = Joiner .on (' ' ).join (scopes );
980+ } else {
981+ scopeClaim = Joiner .on (' ' ).join (defaultScopes );
982+ }
983+ claimsBuilder .setAdditionalClaims (Collections .singletonMap ("scope" , scopeClaim ));
962984 } else {
963- claimsBuilder . setAdditionalClaims (
964- Collections . singletonMap ( "scope" , Joiner . on ( ' ' ). join ( defaultScopes ) ));
985+ // otherwise, use audience with the uri.
986+ claimsBuilder . setAudience ( getUriForSelfSignedJWT ( uri ). toString ( ));
965987 }
966988 return JwtCredentials .newBuilder ()
967989 .setPrivateKey (privateKey )
@@ -974,8 +996,10 @@ JwtCredentials createSelfSignedJwtCredentials(final URI uri) {
974996 @ Override
975997 public void getRequestMetadata (
976998 final URI uri , Executor executor , final RequestMetadataCallback callback ) {
977- if (useJWTAccessWithScope ) {
999+ if (useJwtAccessWithScope ) {
9781000 // This will call getRequestMetadata(URI uri), which handles self signed JWT logic.
1001+ // Self signed JWT doesn't use network, so here we do a blocking call to improve
1002+ // efficiency. executor will be ignored since it is intended for async operation.
9791003 blockingGetToCallback (uri , callback );
9801004 } else {
9811005 super .getRequestMetadata (uri , executor , callback );
@@ -985,22 +1009,31 @@ public void getRequestMetadata(
9851009 /** Provide the request metadata by putting an access JWT directly in the metadata. */
9861010 @ Override
9871011 public Map <String , List <String >> getRequestMetadata (URI uri ) throws IOException {
988- if (createScopedRequired ()) {
989- if (!useJWTAccessWithScope ) {
990- throw new IOException (
991- "Scopes are not configured for service account. Specify the scopes"
992- + " by calling createScoped or passing scopes to constructor." );
993- } else if (uri == null ) {
994- throw new IOException ("Scopes and uri are not configured for service account." );
995- }
1012+ if (createScopedRequired () && uri == null ) {
1013+ throw new IOException (
1014+ "Scopes and uri are not configured for service account. Specify the scopes"
1015+ + " by calling createScoped or passing scopes to constructor or"
1016+ + " providing uri to getRequestMetadata." );
9961017 }
997- if (useJWTAccessWithScope ) {
998- JwtCredentials jwtCredentials = createSelfSignedJwtCredentials (uri );
999- Map <String , List <String >> requestMetadata = jwtCredentials .getRequestMetadata (uri );
1000- return addQuotaProjectIdToRequestMetadata (quotaProjectId , requestMetadata );
1001- } else {
1018+
1019+ // If scopes are provided but we cannot use self signed JWT, then use scopes in the normal oauth
1020+ // way.
1021+ if (!createScopedRequired () && !useJwtAccessWithScope ) {
10021022 return super .getRequestMetadata (uri );
10031023 }
1024+
1025+ // If scopes are provided and self signed JWT can be used, use self signed JWT with scopes.
1026+ // Otherwise, use self signed JWT with uri as the audience.
1027+ JwtCredentials jwtCredentials ;
1028+ if (!createScopedRequired () && useJwtAccessWithScope ) {
1029+ // Create JWT credentials with the scopes.
1030+ jwtCredentials = createSelfSignedJwtCredentials (null );
1031+ } else {
1032+ // Create JWT credentials with the uri as audience.
1033+ jwtCredentials = createSelfSignedJwtCredentials (uri );
1034+ }
1035+ Map <String , List <String >> requestMetadata = jwtCredentials .getRequestMetadata (null );
1036+ return addQuotaProjectIdToRequestMetadata (quotaProjectId , requestMetadata );
10041037 }
10051038
10061039 @ SuppressWarnings ("unused" )
@@ -1037,7 +1070,7 @@ public static class Builder extends GoogleCredentials.Builder {
10371070 private HttpTransportFactory transportFactory ;
10381071 private String quotaProjectId ;
10391072 private int lifetime = DEFAULT_LIFETIME_IN_SECONDS ;
1040- private boolean useJWTAccessWithScope = false ;
1073+ private boolean useJwtAccessWithScope = false ;
10411074
10421075 protected Builder () {}
10431076
@@ -1054,7 +1087,7 @@ protected Builder(ServiceAccountCredentials credentials) {
10541087 this .projectId = credentials .projectId ;
10551088 this .quotaProjectId = credentials .quotaProjectId ;
10561089 this .lifetime = credentials .lifetime ;
1057- this .useJWTAccessWithScope = credentials .useJWTAccessWithScope ;
1090+ this .useJwtAccessWithScope = credentials .useJwtAccessWithScope ;
10581091 }
10591092
10601093 public Builder setClientId (String clientId ) {
@@ -1119,8 +1152,8 @@ public Builder setLifetime(int lifetime) {
11191152 return this ;
11201153 }
11211154
1122- public Builder setUseJWTAccessWithScope (boolean useJWTAccessWithScope ) {
1123- this .useJWTAccessWithScope = useJWTAccessWithScope ;
1155+ public Builder setUseJwtAccessWithScope (boolean useJwtAccessWithScope ) {
1156+ this .useJwtAccessWithScope = useJwtAccessWithScope ;
11241157 return this ;
11251158 }
11261159
@@ -1172,8 +1205,8 @@ public int getLifetime() {
11721205 return lifetime ;
11731206 }
11741207
1175- public boolean getUseJWTAccessWithScope () {
1176- return useJWTAccessWithScope ;
1208+ public boolean getUseJwtAccessWithScope () {
1209+ return useJwtAccessWithScope ;
11771210 }
11781211
11791212 public ServiceAccountCredentials build () {
@@ -1190,7 +1223,7 @@ public ServiceAccountCredentials build() {
11901223 projectId ,
11911224 quotaProjectId ,
11921225 lifetime ,
1193- useJWTAccessWithScope );
1226+ useJwtAccessWithScope );
11941227 }
11951228 }
11961229}
0 commit comments