From 496f81856fe5be3fd3a4048f401b1ff68aee60da Mon Sep 17 00:00:00 2001 From: AydartsyanGrisha Date: Fri, 10 Jul 2026 12:54:38 +0500 Subject: [PATCH 1/3] Add method to get oidc refresh token --- src/main/java/Diadoc/Api/DiadocApi.java | 38 ++++++++- .../Api/auth/oidc/OidcAuthenticator.java | 81 +++++++++++++++++++ .../auth/oidc/OidcRefreshTokenProvider.java | 57 +++++++++++++ .../auth/oidc/OidcTokenRefreshException.java | 8 ++ .../Api/auth/oidc/OidcTokenResponse.java | 23 ++++++ 5 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/main/java/Diadoc/Api/auth/oidc/OidcAuthenticator.java create mode 100644 src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java create mode 100644 src/main/java/Diadoc/Api/auth/oidc/OidcTokenRefreshException.java create mode 100644 src/main/java/Diadoc/Api/auth/oidc/OidcTokenResponse.java diff --git a/src/main/java/Diadoc/Api/DiadocApi.java b/src/main/java/Diadoc/Api/DiadocApi.java index d5f12557..3c2615fb 100644 --- a/src/main/java/Diadoc/Api/DiadocApi.java +++ b/src/main/java/Diadoc/Api/DiadocApi.java @@ -2,6 +2,7 @@ import Diadoc.Api.auth.*; import Diadoc.Api.auth.oidc.OidcAuthManager; +import Diadoc.Api.auth.oidc.OidcRefreshTokenProvider; import Diadoc.Api.auth.oidc.TokenProvider; import Diadoc.Api.counteragent.CounteragentClient; import Diadoc.Api.counteragentGroup.CounteragentGroupClient; @@ -105,6 +106,41 @@ public DiadocApi(String apiClientId, String url) { this(apiClientId, url, null, null); } + /** + "Quick start" of authentication according to the OpenID Connect scheme: the access token will be automatically + * received (and cached) by the integrator's refresh token when the API was first accessed. Address + * identity.kontur.ru server is used by default. + * + * @param apiClientId the integrator's client identifier (Client Id from your personal account), also used as OIDC client_id + * @param oidcClientSecret secret key of the integrator application (Client Secret) + * @param oidcRefreshToken Refresh Integrator Token + * @param url address of the Diadoc server + */ + public static DiadocApi createWithOidcRefreshToken(String apiClientId, String oidcClientSecret, String oidcRefreshToken, String url) { + return createWithOidcRefreshToken(apiClientId, oidcClientSecret, oidcRefreshToken, url, null, null, null); + } + + /** + * "Quick start" of authentication according to the OpenID Connect scheme with advanced settings. + * See. {@link #createWithOidcRefreshToken(String, String, String, String)}. + * + * @param oidcBaseUrl the address of the OpenID Connect server, e.g. {@code "https://identity.kontur.ru"}. + * If {@code null}, the default address is used. + */ + public static DiadocApi createWithOidcRefreshToken( + String apiClientId, + String oidcClientSecret, + String oidcRefreshToken, + String url, + @Nullable String oidcBaseUrl, + @Nullable HttpHost proxyHost, + @Nullable ConnectionSettings connectionSettings) { + TokenProvider tokenProvider = oidcBaseUrl == null + ? new OidcRefreshTokenProvider(apiClientId, oidcClientSecret, oidcRefreshToken) + : new OidcRefreshTokenProvider(apiClientId, oidcClientSecret, oidcRefreshToken, oidcBaseUrl); + return new DiadocApi(tokenProvider, url, proxyHost, connectionSettings); + } + public AuthenticateClient getAuthClient() { return authClient; } @@ -225,4 +261,4 @@ public DiadocHttpClient getDiadocHttpClient() { public void setSolutionInfo(String solutionInfo) { diadocHttpClient.setSolutionInfo(solutionInfo); } -} +} \ No newline at end of file diff --git a/src/main/java/Diadoc/Api/auth/oidc/OidcAuthenticator.java b/src/main/java/Diadoc/Api/auth/oidc/OidcAuthenticator.java new file mode 100644 index 00000000..25d61b3f --- /dev/null +++ b/src/main/java/Diadoc/Api/auth/oidc/OidcAuthenticator.java @@ -0,0 +1,81 @@ +package Diadoc.Api.auth.oidc; + +import Diadoc.Api.exceptions.DiadocSdkException; +import Diadoc.Api.helpers.Tools; +import com.google.gson.Gson; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +public final class OidcAuthenticator { + + private static final String DEFAULT_OIDC_BASE_URL = "https://identity.kontur.ru"; + private static final String OIDC_TOKEN_ENDPOINT_PATH = "/connect/token"; + + private OidcAuthenticator() { + } + + public static String authenticateWithOidc(String clientId, String clientSecret, String refreshToken) throws DiadocSdkException { + return authenticateWithOidc(clientId, clientSecret, refreshToken, DEFAULT_OIDC_BASE_URL); + } + + public static String authenticateWithOidc(String clientId, String clientSecret, String refreshToken, String oidcBaseUrl) throws DiadocSdkException { + if (Tools.isNullOrEmpty(clientId)) { + throw new IllegalArgumentException("clientId cannot be empty or null"); + } + if (Tools.isNullOrEmpty(clientSecret)) { + throw new IllegalArgumentException("clientSecret cannot be empty or null"); + } + if (Tools.isNullOrEmpty(refreshToken)) { + throw new IllegalArgumentException("refreshToken cannot be empty or null"); + } + + String baseUrl = Tools.isNullOrEmpty(oidcBaseUrl) ? DEFAULT_OIDC_BASE_URL : oidcBaseUrl; + OidcTokenResponse response = performOidcTokenRequest(baseUrl, clientId, clientSecret, refreshToken); + return response.getAccessToken(); + } + + private static OidcTokenResponse performOidcTokenRequest(String baseUrl, String clientId, String clientSecret, String refreshToken) throws DiadocSdkException { + List parameters = new ArrayList<>(); + parameters.add(new BasicNameValuePair("grant_type", "refresh_token")); + parameters.add(new BasicNameValuePair("refresh_token", refreshToken)); + parameters.add(new BasicNameValuePair("client_id", clientId)); + parameters.add(new BasicNameValuePair("client_secret", clientSecret)); + + HttpPost request = new HttpPost(baseUrl + OIDC_TOKEN_ENDPOINT_PATH); + request.setEntity(new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8)); + + try (CloseableHttpClient httpClient = HttpClients.createDefault(); + CloseableHttpResponse httpResponse = httpClient.execute(request)) { + String json = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); + + int statusCode = httpResponse.getStatusLine().getStatusCode(); + if (statusCode != 200) { + throw new DiadocSdkException( + "OIDC token endpoint returned status " + statusCode + ". Response body: " + json); + } + + return parseOidcTokenResponse(json); + } catch (IOException e) { + throw new DiadocSdkException(e); + } + } + + private static OidcTokenResponse parseOidcTokenResponse(String json) throws DiadocSdkException { + OidcTokenResponse result = new Gson().fromJson(json, OidcTokenResponse.class); + if (result == null || Tools.isNullOrEmpty(result.getAccessToken())) { + throw new DiadocSdkException("OIDC token endpoint returned response without access_token. Response body: " + json); + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java b/src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java new file mode 100644 index 00000000..8f8ed2d2 --- /dev/null +++ b/src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java @@ -0,0 +1,57 @@ +package Diadoc.Api.auth.oidc; + +import Diadoc.Api.exceptions.DiadocSdkException; + +public class OidcRefreshTokenProvider implements TokenProvider { + + private final String clientId; + private final String clientSecret; + private final String refreshToken; + private final String oidcBaseUrl; + + private volatile String cachedAccessToken; + + public OidcRefreshTokenProvider(String clientId, String clientSecret, String refreshToken) { + this(clientId, clientSecret, refreshToken, null); + } + + public OidcRefreshTokenProvider(String clientId, String clientSecret, String refreshToken, String oidcBaseUrl) { + this.clientId = clientId; + this.clientSecret = clientSecret; + this.refreshToken = refreshToken; + this.oidcBaseUrl = oidcBaseUrl; + } + + @Override + public String getToken() { + String token = cachedAccessToken; + if (token != null) { + return token; + } + synchronized (this) { + token = cachedAccessToken; + if (token != null) { + return token; + } + return fetchAndCacheToken(); + } + } + + public String refresh() { + synchronized (this) { + return fetchAndCacheToken(); + } + } + + private String fetchAndCacheToken() { + try { + String token = oidcBaseUrl == null + ? OidcAuthenticator.authenticateWithOidc(clientId, clientSecret, refreshToken) + : OidcAuthenticator.authenticateWithOidc(clientId, clientSecret, refreshToken, oidcBaseUrl); + cachedAccessToken = token; + return token; + } catch (DiadocSdkException e) { + throw new OidcTokenRefreshException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/Diadoc/Api/auth/oidc/OidcTokenRefreshException.java b/src/main/java/Diadoc/Api/auth/oidc/OidcTokenRefreshException.java new file mode 100644 index 00000000..29e03548 --- /dev/null +++ b/src/main/java/Diadoc/Api/auth/oidc/OidcTokenRefreshException.java @@ -0,0 +1,8 @@ +package Diadoc.Api.auth.oidc; + +public class OidcTokenRefreshException extends RuntimeException { + + public OidcTokenRefreshException(Throwable cause) { + super("Can't get access-token by refresh-token", cause); + } +} \ No newline at end of file diff --git a/src/main/java/Diadoc/Api/auth/oidc/OidcTokenResponse.java b/src/main/java/Diadoc/Api/auth/oidc/OidcTokenResponse.java new file mode 100644 index 00000000..ffdab342 --- /dev/null +++ b/src/main/java/Diadoc/Api/auth/oidc/OidcTokenResponse.java @@ -0,0 +1,23 @@ +package Diadoc.Api.auth.oidc; + +import com.google.gson.annotations.SerializedName; + +public class OidcTokenResponse { + + @SerializedName("access_token") + private String accessToken; + + @SerializedName("token_type") + private String tokenType; + + @SerializedName("expires_in") + private int expiresIn; + + @SerializedName("refresh_token") + private String refreshToken; + + public String getAccessToken() { return accessToken; } + public String getTokenType() { return tokenType; } + public int getExpiresIn() { return expiresIn; } + public String getRefreshToken() { return refreshToken; } +} \ No newline at end of file From ca6e5a44def32e03dfa36e33146b01bc366cde9e Mon Sep 17 00:00:00 2001 From: AydartsyanGrisha Date: Fri, 17 Jul 2026 11:16:17 +0500 Subject: [PATCH 2/3] Review-fix --- src/main/java/Diadoc/Api/DiadocApi.java | 9 ++-- .../Api/auth/oidc/OidcAuthenticator.java | 51 ++++++++++++++++--- .../auth/oidc/OidcRefreshTokenProvider.java | 50 +++++++++++++----- 3 files changed, 85 insertions(+), 25 deletions(-) diff --git a/src/main/java/Diadoc/Api/DiadocApi.java b/src/main/java/Diadoc/Api/DiadocApi.java index 3c2615fb..eeb8ec9f 100644 --- a/src/main/java/Diadoc/Api/DiadocApi.java +++ b/src/main/java/Diadoc/Api/DiadocApi.java @@ -124,8 +124,8 @@ public static DiadocApi createWithOidcRefreshToken(String apiClientId, String oi * "Quick start" of authentication according to the OpenID Connect scheme with advanced settings. * See. {@link #createWithOidcRefreshToken(String, String, String, String)}. * - * @param oidcBaseUrl the address of the OpenID Connect server, e.g. {@code "https://identity.kontur.ru"}. - * If {@code null}, the default address is used. + * @param oidcBaseUrl адрес сервера OpenID Connect, например {@code "https://identity.kontur.ru"}. + * Если {@code null}, используется адрес по умолчанию. */ public static DiadocApi createWithOidcRefreshToken( String apiClientId, @@ -135,9 +135,8 @@ public static DiadocApi createWithOidcRefreshToken( @Nullable String oidcBaseUrl, @Nullable HttpHost proxyHost, @Nullable ConnectionSettings connectionSettings) { - TokenProvider tokenProvider = oidcBaseUrl == null - ? new OidcRefreshTokenProvider(apiClientId, oidcClientSecret, oidcRefreshToken) - : new OidcRefreshTokenProvider(apiClientId, oidcClientSecret, oidcRefreshToken, oidcBaseUrl); + TokenProvider tokenProvider = new OidcRefreshTokenProvider( + apiClientId, oidcClientSecret, oidcRefreshToken, oidcBaseUrl, proxyHost, connectionSettings); return new DiadocApi(tokenProvider, url, proxyHost, connectionSettings); } diff --git a/src/main/java/Diadoc/Api/auth/oidc/OidcAuthenticator.java b/src/main/java/Diadoc/Api/auth/oidc/OidcAuthenticator.java index 25d61b3f..e6b1459c 100644 --- a/src/main/java/Diadoc/Api/auth/oidc/OidcAuthenticator.java +++ b/src/main/java/Diadoc/Api/auth/oidc/OidcAuthenticator.java @@ -1,16 +1,21 @@ package Diadoc.Api.auth.oidc; +import Diadoc.Api.ConnectionSettings; import Diadoc.Api.exceptions.DiadocSdkException; import Diadoc.Api.helpers.Tools; import com.google.gson.Gson; +import org.apache.http.HttpHost; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -25,11 +30,21 @@ public final class OidcAuthenticator { private OidcAuthenticator() { } - public static String authenticateWithOidc(String clientId, String clientSecret, String refreshToken) throws DiadocSdkException { - return authenticateWithOidc(clientId, clientSecret, refreshToken, DEFAULT_OIDC_BASE_URL); + public static OidcTokenResponse authenticateWithOidc(String clientId, String clientSecret, String refreshToken) throws DiadocSdkException { + return authenticateWithOidc(clientId, clientSecret, refreshToken, null, null, null); } - public static String authenticateWithOidc(String clientId, String clientSecret, String refreshToken, String oidcBaseUrl) throws DiadocSdkException { + public static OidcTokenResponse authenticateWithOidc(String clientId, String clientSecret, String refreshToken, @Nullable String oidcBaseUrl) throws DiadocSdkException { + return authenticateWithOidc(clientId, clientSecret, refreshToken, oidcBaseUrl, null, null); + } + + public static OidcTokenResponse authenticateWithOidc( + String clientId, + String clientSecret, + String refreshToken, + @Nullable String oidcBaseUrl, + @Nullable HttpHost proxyHost, + @Nullable ConnectionSettings connectionSettings) throws DiadocSdkException { if (Tools.isNullOrEmpty(clientId)) { throw new IllegalArgumentException("clientId cannot be empty or null"); } @@ -41,11 +56,16 @@ public static String authenticateWithOidc(String clientId, String clientSecret, } String baseUrl = Tools.isNullOrEmpty(oidcBaseUrl) ? DEFAULT_OIDC_BASE_URL : oidcBaseUrl; - OidcTokenResponse response = performOidcTokenRequest(baseUrl, clientId, clientSecret, refreshToken); - return response.getAccessToken(); + return performOidcTokenRequest(baseUrl, clientId, clientSecret, refreshToken, proxyHost, connectionSettings); } - private static OidcTokenResponse performOidcTokenRequest(String baseUrl, String clientId, String clientSecret, String refreshToken) throws DiadocSdkException { + private static OidcTokenResponse performOidcTokenRequest( + String baseUrl, + String clientId, + String clientSecret, + String refreshToken, + @Nullable HttpHost proxyHost, + @Nullable ConnectionSettings connectionSettings) throws DiadocSdkException { List parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair("grant_type", "refresh_token")); parameters.add(new BasicNameValuePair("refresh_token", refreshToken)); @@ -55,7 +75,7 @@ private static OidcTokenResponse performOidcTokenRequest(String baseUrl, String HttpPost request = new HttpPost(baseUrl + OIDC_TOKEN_ENDPOINT_PATH); request.setEntity(new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8)); - try (CloseableHttpClient httpClient = HttpClients.createDefault(); + try (CloseableHttpClient httpClient = buildHttpClient(proxyHost, connectionSettings); CloseableHttpResponse httpResponse = httpClient.execute(request)) { String json = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); @@ -71,6 +91,23 @@ private static OidcTokenResponse performOidcTokenRequest(String baseUrl, String } } + private static CloseableHttpClient buildHttpClient(@Nullable HttpHost proxyHost, @Nullable ConnectionSettings connectionSettings) { + HttpClientBuilder httpClientBuilder = HttpClients.custom(); + + if (connectionSettings != null) { + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(connectionSettings.getMaxTotalConnections()); + connectionManager.setDefaultMaxPerRoute(connectionSettings.getMaxConnectionsPerRoute()); + httpClientBuilder.setConnectionManager(connectionManager); + } + + if (proxyHost != null) { + httpClientBuilder.setProxy(proxyHost); + } + + return httpClientBuilder.build(); + } + private static OidcTokenResponse parseOidcTokenResponse(String json) throws DiadocSdkException { OidcTokenResponse result = new Gson().fromJson(json, OidcTokenResponse.class); if (result == null || Tools.isNullOrEmpty(result.getAccessToken())) { diff --git a/src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java b/src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java index 8f8ed2d2..32382978 100644 --- a/src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java +++ b/src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java @@ -1,37 +1,55 @@ package Diadoc.Api.auth.oidc; +import Diadoc.Api.ConnectionSettings; import Diadoc.Api.exceptions.DiadocSdkException; +import org.apache.http.HttpHost; +import org.jetbrains.annotations.Nullable; public class OidcRefreshTokenProvider implements TokenProvider { + private static final long EXPIRATION_SAFETY_MARGIN_MILLIS = 60_000; + private final String clientId; private final String clientSecret; private final String refreshToken; private final String oidcBaseUrl; + private final HttpHost proxyHost; + private final ConnectionSettings connectionSettings; private volatile String cachedAccessToken; + private volatile long cachedAccessTokenExpiresAtMillis; public OidcRefreshTokenProvider(String clientId, String clientSecret, String refreshToken) { - this(clientId, clientSecret, refreshToken, null); + this(clientId, clientSecret, refreshToken, null, null, null); + } + + public OidcRefreshTokenProvider(String clientId, String clientSecret, String refreshToken, @Nullable String oidcBaseUrl) { + this(clientId, clientSecret, refreshToken, oidcBaseUrl, null, null); } - public OidcRefreshTokenProvider(String clientId, String clientSecret, String refreshToken, String oidcBaseUrl) { + public OidcRefreshTokenProvider( + String clientId, + String clientSecret, + String refreshToken, + @Nullable String oidcBaseUrl, + @Nullable HttpHost proxyHost, + @Nullable ConnectionSettings connectionSettings) { this.clientId = clientId; this.clientSecret = clientSecret; this.refreshToken = refreshToken; this.oidcBaseUrl = oidcBaseUrl; + this.proxyHost = proxyHost; + this.connectionSettings = connectionSettings; } @Override public String getToken() { - String token = cachedAccessToken; - if (token != null) { - return token; + if (isCachedTokenValid()) { + return cachedAccessToken; } synchronized (this) { - token = cachedAccessToken; - if (token != null) { - return token; + if (isCachedTokenValid()) { + return cachedAccessToken; } return fetchAndCacheToken(); } @@ -43,13 +61,19 @@ public String refresh() { } } + private boolean isCachedTokenValid() { + return cachedAccessToken != null && System.currentTimeMillis() < cachedAccessTokenExpiresAtMillis; + } + private String fetchAndCacheToken() { try { - String token = oidcBaseUrl == null - ? OidcAuthenticator.authenticateWithOidc(clientId, clientSecret, refreshToken) - : OidcAuthenticator.authenticateWithOidc(clientId, clientSecret, refreshToken, oidcBaseUrl); - cachedAccessToken = token; - return token; + OidcTokenResponse response = OidcAuthenticator.authenticateWithOidc( + clientId, clientSecret, refreshToken, oidcBaseUrl, proxyHost, connectionSettings); + cachedAccessToken = response.getAccessToken(); + cachedAccessTokenExpiresAtMillis = System.currentTimeMillis() + + response.getExpiresIn() * 1000L + - EXPIRATION_SAFETY_MARGIN_MILLIS; + return cachedAccessToken; } catch (DiadocSdkException e) { throw new OidcTokenRefreshException(e); } From c841fdf964128757867bbe09d73a4c2c3197fcb7 Mon Sep 17 00:00:00 2001 From: AydartsyanGrisha Date: Mon, 20 Jul 2026 12:04:24 +0500 Subject: [PATCH 3/3] Review-fix: add lock object --- .../auth/oidc/OidcRefreshTokenProvider.java | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java b/src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java index 32382978..61f11364 100644 --- a/src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java +++ b/src/main/java/Diadoc/Api/auth/oidc/OidcRefreshTokenProvider.java @@ -15,9 +15,9 @@ public class OidcRefreshTokenProvider implements TokenProvider { private final String oidcBaseUrl; private final HttpHost proxyHost; private final ConnectionSettings connectionSettings; + private final Object tokenRefreshLock = new Object(); - private volatile String cachedAccessToken; - private volatile long cachedAccessTokenExpiresAtMillis; + private volatile CachedToken cachedToken; public OidcRefreshTokenProvider(String clientId, String clientSecret, String refreshToken) { this(clientId, clientSecret, refreshToken, null, null, null); @@ -44,38 +44,59 @@ public OidcRefreshTokenProvider( @Override public String getToken() { - if (isCachedTokenValid()) { - return cachedAccessToken; + CachedToken token = cachedToken; + if (isValid(token)) { + return token.accessToken(); } - synchronized (this) { - if (isCachedTokenValid()) { - return cachedAccessToken; + synchronized (tokenRefreshLock) { + token = cachedToken; + if (isValid(token)) { + return token.accessToken(); } return fetchAndCacheToken(); } } public String refresh() { - synchronized (this) { + synchronized (tokenRefreshLock) { return fetchAndCacheToken(); } } - private boolean isCachedTokenValid() { - return cachedAccessToken != null && System.currentTimeMillis() < cachedAccessTokenExpiresAtMillis; + private static boolean isValid(@Nullable CachedToken token) { + return token != null && System.currentTimeMillis() < token.expiresAtMillis(); } private String fetchAndCacheToken() { try { OidcTokenResponse response = OidcAuthenticator.authenticateWithOidc( clientId, clientSecret, refreshToken, oidcBaseUrl, proxyHost, connectionSettings); - cachedAccessToken = response.getAccessToken(); - cachedAccessTokenExpiresAtMillis = System.currentTimeMillis() + long expiresAtMillis = System.currentTimeMillis() + response.getExpiresIn() * 1000L - EXPIRATION_SAFETY_MARGIN_MILLIS; - return cachedAccessToken; + CachedToken token = new CachedToken(response.getAccessToken(), expiresAtMillis); + cachedToken = token; + return token.accessToken(); } catch (DiadocSdkException e) { throw new OidcTokenRefreshException(e); } } + + private static final class CachedToken { + private final String accessToken; + private final long expiresAtMillis; + + private CachedToken(String accessToken, long expiresAtMillis) { + this.accessToken = accessToken; + this.expiresAtMillis = expiresAtMillis; + } + + private String accessToken() { + return accessToken; + } + + private long expiresAtMillis() { + return expiresAtMillis; + } + } } \ No newline at end of file