Skip to content
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ The `ConfidentialClient` refreshes access tokens proactively before their actual
Default behaviour:
- A 30 second (30,000 ms) proactive offset is applied automatically.
- Calls to `getAccessToken()` (or `getAccessToken(false)`) reuse the cached token while it is still considered valid under this adjusted expiry.
- `getAccessToken(true)` forces a fresh token unless one was very recently refreshed (within 5 seconds) to avoid unnecessary duplicate requests.
- `getAccessToken(true)` forces a fresh token unless one was very recently refreshed (within 5 seconds) to avoid unnecessary duplicate requests from concurrent threads.

You can override the proactive offset by configuring it in `RequestOptions`:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public class ConfidentialClient implements OAuth2Client {
public ConfidentialClient(final String configPath)
throws AuthServerMetadataContentException, AuthServerMetadataException,
ConfigurationException {
this(new Configuration(configPath));
this(new Configuration(configPath), RequestOptions.builder().build());
}

/**
Expand Down Expand Up @@ -142,7 +142,7 @@ protected ConfidentialClient(final String configPath, final TokenRequestBuilder
throws AuthServerMetadataContentException,
AuthServerMetadataException,
ConfigurationException {
this(new Configuration(configPath));
this(new Configuration(configPath), RequestOptions.builder().build());
this.tokenRequestBuilder = tokReqBuilder.uri(this.providerMetadata.getTokenEndpointURI());
}

Expand All @@ -160,7 +160,7 @@ protected ConfidentialClient(final String configPath, final TokenRequestBuilder
protected ConfidentialClient(final Configuration config, final TokenRequestBuilder tokReqBuilder)
throws AuthServerMetadataContentException,
AuthServerMetadataException {
this(config);
this(config, RequestOptions.builder().build());
this.tokenRequestBuilder = tokReqBuilder.uri(this.providerMetadata.getTokenEndpointURI());
}

Expand Down Expand Up @@ -194,7 +194,7 @@ protected ConfidentialClient(final Configuration config, final TokenRequestBuild
* @throws AccessTokenException If it can't make a successful request or parse the TokenRequest.
* @throws SigningJwsException If the signing of the JWS fails.
*/
public String getAccessToken(boolean forceRefresh) throws AccessTokenException, SigningJwsException {
public synchronized String getAccessToken(boolean forceRefresh) throws AccessTokenException, SigningJwsException {
if (this.isCachedTokenValid()) {
if (!forceRefresh) {
LOGGER.info("Retrieved access token which expires in: {} seconds", TimeUnit.MILLISECONDS.toSeconds(this.accessTokenExpireTime - System.currentTimeMillis()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,31 @@ void getAccessTokenForceRefreshThenCachedReturnsCorrectTokens() throws Exception
verify(harness.httpRequestMock, times(1)).send();
}

@Test
void getAccessTokenTwoDifferentThreadsSimultaneouslyOnlyFetchesOnce() throws Exception {
TestHarness harness = createClientWithTokens(899, "threadedToken");

Runnable task = () -> {
String token;
try {
token = harness.client.getAccessToken();
} catch (AccessTokenException | SigningJwsException e) {
throw new RuntimeException(e);
}
assertEquals("threadedToken", token);
};

Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);

thread1.start();
thread2.start();

thread1.join();
thread2.join();

verify(harness.httpRequestMock, times(1)).send();
}

@Test
void forceRefreshWithinGracePeriodReturnsCachedToken() throws Exception {
Expand Down