Skip to content

Commit f0be91a

Browse files
authored
feat: [Connectivity] Introduce OAuth2Option for token cache parameters (SAP#860)
Co-authored-by: Roshin Rajan Panackal <[email protected]>
1 parent 213c929 commit f0be91a

File tree

7 files changed

+139
-11
lines changed

7 files changed

+139
-11
lines changed

cloudplatform/connectivity-oauth/pom.xml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@
134134
<groupId>org.apache.commons</groupId>
135135
<artifactId>commons-lang3</artifactId>
136136
</dependency>
137+
<dependency>
138+
<groupId>com.google.guava</groupId>
139+
<artifactId>guava</artifactId>
140+
</dependency>
137141
<!-- scope "provided" -->
138142
<dependency>
139143
<groupId>org.projectlombok</groupId>
@@ -192,11 +196,6 @@
192196
<artifactId>testutil</artifactId>
193197
<scope>test</scope>
194198
</dependency>
195-
<dependency>
196-
<groupId>com.google.guava</groupId>
197-
<artifactId>guava</artifactId>
198-
<scope>test</scope>
199-
</dependency>
200199
</dependencies>
201200
<build>
202201
<plugins>

cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/DefaultOAuth2PropertySupplier.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ public OAuth2Options getOAuth2Options()
134134
{
135135
final OAuth2Options.Builder builder = OAuth2Options.builder();
136136
options.getOption(OAuth2Options.TokenRetrievalTimeout.class).peek(builder::withTimeLimiter);
137+
options.getOption(OAuth2Options.TokenCacheParameters.class).peek(builder::withTokenCacheParameters);
137138
return builder.build();
138139
}
139140

cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/OAuth2Options.java

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import javax.annotation.Nonnull;
99
import javax.annotation.Nullable;
1010

11+
import com.google.common.annotations.Beta;
1112
import com.sap.cloud.sdk.cloudplatform.connectivity.ServiceBindingDestinationOptions.OptionsEnhancer;
1213
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceConfiguration.TimeLimiterConfiguration;
1314

@@ -33,11 +34,23 @@ public final class OAuth2Options
3334
* @since 5.12.0
3435
*/
3536
public static final TimeLimiterConfiguration DEFAULT_TIMEOUT = TimeLimiterConfiguration.of(Duration.ofSeconds(10));
37+
38+
/**
39+
* Default token cache configuration used by {@link OAuth2Service}. Effective defaults: 1 hour duration, 1000
40+
* entries, 30 seconds delta and cache statistics disabled.
41+
*
42+
* @see com.sap.cloud.security.xsuaa.tokenflows.TokenCacheConfiguration#DEFAULT
43+
* @since 5.21.0
44+
*/
45+
public static final TokenCacheParameters DEFAULT_TOKEN_CACHE_PARAMETERS =
46+
TokenCacheParameters.of(Duration.ofHours(1), 1000, Duration.ofSeconds(30));
47+
3648
/**
3749
* The default {@link OAuth2Options} instance that does not alter the token retrieval process and does not use mTLS
3850
* for the target system connection.
3951
*/
40-
public static final OAuth2Options DEFAULT = new OAuth2Options(false, Map.of(), DEFAULT_TIMEOUT, null);
52+
public static final OAuth2Options DEFAULT =
53+
new OAuth2Options(false, Map.of(), DEFAULT_TIMEOUT, null, DEFAULT_TOKEN_CACHE_PARAMETERS);
4154

4255
private final boolean skipTokenRetrieval;
4356
@Nonnull
@@ -58,6 +71,15 @@ public final class OAuth2Options
5871
@Getter
5972
private final KeyStore clientKeyStore;
6073

74+
/**
75+
* Configuration for caching OAuth2 tokens.
76+
*
77+
* @since 5.21.0
78+
*/
79+
@Nonnull
80+
@Getter
81+
private final TokenCacheParameters tokenCacheParameters;
82+
6183
/**
6284
* Indicates whether to skip the OAuth2 token flow.
6385
*
@@ -101,6 +123,7 @@ public static class Builder
101123
private final Map<String, String> additionalTokenRetrievalParameters = new HashMap<>();
102124
private KeyStore clientKeyStore;
103125
private TimeLimiterConfiguration timeLimiter = DEFAULT_TIMEOUT;
126+
private TokenCacheParameters tokenCacheParameters = DEFAULT_TOKEN_CACHE_PARAMETERS;
104127

105128
/**
106129
* Indicates whether to skip the OAuth2 token flow.
@@ -178,6 +201,21 @@ public Builder withTimeLimiter( @Nonnull final TimeLimiterConfiguration timeLimi
178201
return this;
179202
}
180203

204+
/**
205+
* Set a custom token cache configuration. {@link #DEFAULT_TOKEN_CACHE_PARAMETERS} by default.
206+
*
207+
* @param tokenCacheParameters
208+
* The custom token cache parameters.
209+
* @return This {@link Builder}.
210+
* @since 5.21.0
211+
*/
212+
@Nonnull
213+
public Builder withTokenCacheParameters( @Nonnull final TokenCacheParameters tokenCacheParameters )
214+
{
215+
this.tokenCacheParameters = tokenCacheParameters;
216+
return this;
217+
}
218+
181219
/**
182220
* Creates a new {@link OAuth2Options} instance.
183221
*
@@ -198,7 +236,8 @@ public OAuth2Options build()
198236
skipTokenRetrieval,
199237
new HashMap<>(additionalTokenRetrievalParameters),
200238
timeLimiter,
201-
clientKeyStore);
239+
clientKeyStore,
240+
tokenCacheParameters);
202241
}
203242
}
204243

@@ -214,4 +253,51 @@ public static class TokenRetrievalTimeout implements OptionsEnhancer<TimeLimiter
214253
@Nonnull
215254
private final TimeLimiterConfiguration value;
216255
}
256+
257+
/**
258+
* Configuration for the token <em>response</em> cache used by {@link OAuth2Service}.
259+
*
260+
* <p>
261+
* <strong>Important:</strong> These values are passed to
262+
* {@link com.sap.cloud.security.xsuaa.tokenflows.TokenCacheConfiguration} used by XSUAAs
263+
* {@code DefaultOAuth2TokenService}. This cache stores the HTTP token response (including the token) and it governs
264+
* the cache entry, <em>not</em> the token's lifetime.
265+
*
266+
* <p>
267+
* Expired (or almost expired) tokens are never served, regardless of {@link #cacheDuration} as xsuaa checks
268+
* <code>exp - {@link #tokenExpirationDelta}</code> before returning a cached entry.
269+
*
270+
* @since 5.21.0
271+
*/
272+
@Beta
273+
@Getter
274+
@RequiredArgsConstructor( staticName = "of" )
275+
public static class TokenCacheParameters implements OptionsEnhancer<TokenCacheParameters>
276+
{
277+
/**
278+
* Upper bound for how long a successful token response may remain cached. A cached entry is ignored earlier if
279+
* the token would be (almost) expired.
280+
*/
281+
@Nonnull
282+
private final Duration cacheDuration;
283+
/**
284+
* The maximum number of tokens to cache.
285+
*/
286+
@Nonnull
287+
private final Integer cacheSize;
288+
/**
289+
* The delta to be subtracted from the token expiration time to determine how early should a token be refreshed
290+
* before it expires.
291+
*/
292+
@Nonnull
293+
private final Duration tokenExpirationDelta;
294+
295+
@Override
296+
@Nonnull
297+
public TokenCacheParameters getValue()
298+
{
299+
return this;
300+
}
301+
}
302+
217303
}

cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/OAuth2Service.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import static com.sap.cloud.security.xsuaa.util.UriUtil.expandPath;
44

55
import java.net.URI;
6-
import java.time.Duration;
76
import java.util.HashMap;
87
import java.util.Map;
98
import java.util.concurrent.TimeUnit;
@@ -19,6 +18,7 @@
1918
import com.sap.cloud.environment.servicebinding.api.ServiceIdentifier;
2019
import com.sap.cloud.sdk.cloudplatform.cache.CacheKey;
2120
import com.sap.cloud.sdk.cloudplatform.cache.CacheManager;
21+
import com.sap.cloud.sdk.cloudplatform.connectivity.OAuth2Options.TokenCacheParameters;
2222
import com.sap.cloud.sdk.cloudplatform.connectivity.SecurityLibWorkarounds.ZtisClientIdentity;
2323
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
2424
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationOAuthTokenException;
@@ -91,6 +91,8 @@ class OAuth2Service
9191
@Nonnull
9292
@Getter( AccessLevel.PACKAGE )
9393
private final ResilienceConfiguration resilienceConfiguration;
94+
@Nonnull
95+
private final TokenCacheParameters tokenCacheParameters;
9496

9597
// package-private for testing
9698
@Nonnull
@@ -104,7 +106,13 @@ OAuth2TokenService getTokenService( @Nullable final String tenantId )
104106
private OAuth2TokenService createTokenService( @Nonnull final CacheKey ignored )
105107
{
106108
final var tokenCacheConfiguration =
107-
TokenCacheConfiguration.getInstance(Duration.ofHours(1), 1000, Duration.ofSeconds(30), false);
109+
TokenCacheConfiguration
110+
.getInstance(
111+
tokenCacheParameters.getCacheDuration(),
112+
tokenCacheParameters.getCacheSize(),
113+
tokenCacheParameters.getTokenExpirationDelta(),
114+
false); // disable cache statistics
115+
108116
if( !(identity instanceof ZtisClientIdentity) ) {
109117
return new DefaultOAuth2TokenService(HttpClientFactory.create(identity), tokenCacheConfiguration);
110118
}
@@ -345,6 +353,7 @@ static class Builder
345353
private TenantPropagationStrategy tenantPropagationStrategy = TenantPropagationStrategy.ZID_HEADER;
346354
private final Map<String, String> additionalParameters = new HashMap<>();
347355
private ResilienceConfiguration.TimeLimiterConfiguration timeLimiter = OAuth2Options.DEFAULT_TIMEOUT;
356+
private TokenCacheParameters tokenCacheParameters = OAuth2Options.DEFAULT_TOKEN_CACHE_PARAMETERS;
348357

349358
@Nonnull
350359
Builder withTokenUri( @Nonnull final String tokenUri )
@@ -422,6 +431,13 @@ Builder withTimeLimiter( @Nonnull final ResilienceConfiguration.TimeLimiterConfi
422431
return this;
423432
}
424433

434+
@Nonnull
435+
Builder withTokenCacheParameters( @Nonnull final TokenCacheParameters tokenCacheParameters )
436+
{
437+
this.tokenCacheParameters = tokenCacheParameters;
438+
return this;
439+
}
440+
425441
@Nonnull
426442
OAuth2Service build()
427443
{
@@ -444,13 +460,15 @@ OAuth2Service build()
444460

445461
// copy the additional parameters to prevent accidental manipulation after the `OAuth2Service` instance has been created.
446462
final Map<String, String> additionalParameters = new HashMap<>(this.additionalParameters);
463+
447464
return new OAuth2Service(
448465
tokenUri,
449466
identity,
450467
onBehalfOf,
451468
tenantPropagationStrategy,
452469
additionalParameters,
453-
resilienceConfig);
470+
resilienceConfig,
471+
tokenCacheParameters);
454472
}
455473
}
456474

cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/OAuth2ServiceBindingDestinationLoader.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ DestinationHeaderProvider createHeaderProvider(
326326
.withTenantPropagationStrategyFrom(serviceIdentifier)
327327
.withAdditionalParameters(oAuth2Options.getAdditionalTokenRetrievalParameters())
328328
.withTimeLimiter(oAuth2Options.getTimeLimiter())
329+
.withTokenCacheParameters(oAuth2Options.getTokenCacheParameters())
329330
.build();
330331
return new OAuth2HeaderProvider(oAuth2Service, authHeader);
331332
}

cloudplatform/connectivity-oauth/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DefaultOAuth2PropertySupplierTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,29 @@ void testTimeoutConfiguration()
234234
.isEqualTo(TimeLimiterConfiguration.of(Duration.ofSeconds(100)));
235235
}
236236

237+
@Test
238+
void testTokenCacheConfigurationOption()
239+
{
240+
final ServiceBinding binding =
241+
new ServiceBindingBuilder(ServiceIdentifier.DESTINATION).with("name", "asdf").build();
242+
ServiceBindingDestinationOptions options = ServiceBindingDestinationOptions.forService(binding).build();
243+
244+
sut = new DefaultOAuth2PropertySupplier(options);
245+
246+
assertThat(sut.getOAuth2Options().getTokenCacheParameters())
247+
.isSameAs(OAuth2Options.DEFAULT_TOKEN_CACHE_PARAMETERS);
248+
249+
options =
250+
ServiceBindingDestinationOptions
251+
.forService(binding)
252+
.withOption(OAuth2Options.TokenCacheParameters.of(Duration.ofSeconds(10), 5, Duration.ofSeconds(1)))
253+
.build();
254+
sut = new DefaultOAuth2PropertySupplier(options);
255+
assertThat(sut.getOAuth2Options().getTokenCacheParameters())
256+
.usingRecursiveComparison()
257+
.isEqualTo(OAuth2Options.TokenCacheParameters.of(Duration.ofSeconds(10), 5, Duration.ofSeconds(1)));
258+
}
259+
237260
@RequiredArgsConstructor
238261
private static final class ServiceBindingBuilder
239262
{

release_notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
### ✨ New Functionality
1414

15-
-
15+
- Add `TokenCacheParameters` to `OAuth2Options` to configurate token cache duration, expiration delta and cache size.
1616

1717
### 📈 Improvements
1818

0 commit comments

Comments
 (0)