Class CachingTokenSource
- All Implemented Interfaces:
TokenSource,AutoCloseable
- Direct Known Subclasses:
FederatedAssertionTokenSource,NativeSecretTokenSource,PrivateKeyJwtTokenSource
TokenSource, so the three
mechanisms differ in exactly one method and cannot drift apart in the parts that are not about
the credential at all.
Four guarantees, taken from a cache the platform already runs
These are the guarantees the platform's own server-side token cache provides. The SDK implements them itself because it is a standalone client library with no dependency on platform internals.
- Reuse within validity - a cached token outside the refresh window is returned without touching the token endpoint.
- Refresh-before-expiry skew - a token within sixty seconds of expiry is treated as already expired. The caller's clock and the server's clock are not the same clock, and the token still has to survive the flight time of the operation it was fetched for. Sixty seconds is the platform's own number: the same window the server-side cache uses and the same clock skew the platform admits on a client assertion.
- Single-flight - one thread mints and the rest observe its result, so a thousand threads waking to an expired token produce one token request, not a thousand.
- No poison on failure - a failed mint leaves any previously cached token exactly as it was. A transient outage must not turn a working session into a broken one.
Tokens live only in this object's memory. They are never logged, never written to disk, never
placed in an exception message, and are zeroized as far as the JVM permits on
invalidate() and close().
A transport failure is not presented as a verdict
Each subclass words its own refusal ("the client secret failed", "the platform refused the external token"), and that wording is a VERDICT about the credential. When the token request obtains no usable HTTP answer - nothing arrived, or what arrived could not be read - the subclass's wording would be a false verdict, and a workload told its secret failed goes and rotates a secret that was never the problem. The loop every mint goes through therefore replaces such a failure with one that says no usable decision was received.
It lives here rather than in the subclasses, so a source added later inherits it without
writing anything - PROVIDED it throws with the transport's ApiException as the direct
cause, as the three existing sources do. A source that wraps that exception once more opts out
silently. The subclasses still build their verdict text; it is replaced here, not avoided there.
The question every mint failure is put to is the same one: did the server judge the
credential? A verdict is presented only when it did. The discriminator is the RFC 6749 Section 5.2
error code the token endpoint answers with (TokenEndpointError), because the status
alone cannot tell invalid_grant from invalid_request at 400; the status is the
fallback for a body carrying no code, which is what an edge answering HTML produces. Without this
distinction, ten different failures - 401, 400 invalid_grant, 400 invalid_request, 403, 500, 404,
503 after five retries, and three shapes of 2xx without a usable token - would all read as "the
client secret failed", although exactly one of them is a decision.
Minting binds
Every path that produces a token also binds it to the transport, and accessToken()
re-binds even on a cache hit. Binding is therefore idempotent and the transport cannot be left
holding a token this source has already discarded.
-
Method Summary
Modifier and TypeMethodDescriptionfinal StringReturns a token that is valid now, minting one if none is cached or the cached one is inside the refresh window, and binding whatever it returns to the transport.final InstantThe instant the currently cached token expires.voidclose()Discards the cached token.final InstantWhen the token currently in force expires, if this source knows.final booleanWhether a token is currently cached.voidDiscards the cached token and unbinds it from the transport.Methods inherited from class java.lang.Object
equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface co.ankatech.ankasecure.sdk.auth.TokenSource
mechanism
-
Method Details
-
accessToken
Description copied from interface:TokenSourceReturns a token that is valid now, minting one if none is cached or the cached one is inside the refresh window, and binding whatever it returns to the transport.Concurrent callers of a stale source do not stampede the token endpoint: exactly one mints and the rest observe its result.
- Specified by:
accessTokenin interfaceTokenSource- Returns:
- the ANKASecure access token; never
nulland never blank - Throws:
AnkaSecureSdkException- if a token cannot be obtained. A failure leaves any previously cached token untouched rather than poisoning the cache with the failure.
-
invalidate
public void invalidate()Discards the cached token and unbinds it from the transport.Not
final. A source whose credential is itself produced by something that caches - the federated one, whose provider may hold an external token under that issuer's stated lifetime - needs invalidation to reach through to it; otherwise a caller invalidating after an external secret was rotated would get a fresh ANKASecure mint built from the same stale external token, which is exactly the failure they are clearing. An override extends the reach; it must still callsuper.invalidate(), because the unbinding below is the part no subclass may skip.- Specified by:
invalidatein interfaceTokenSource
-
close
public void close()Discards the cached token. Subclasses holding a credential for re-minting override this, release it, and callsuper.close().It performs the discard directly rather than by calling
invalidate(). That method is overridable, so routing close through it would re-enter the override - closing a federated source would tell its provider twice that its token was dead, the second time after the provider had already been closed. Closing releases; invalidating is a caller saying a credential went stale. They are different statements and only one of them belongs in a shutdown path.- Specified by:
closein interfaceAutoCloseable- Specified by:
closein interfaceTokenSource
-
currentTokenExpiry
Description copied from interface:TokenSourceWhen the token currently in force expires, if this source knows.Known for any source that minted its own token, because the issuer states the lifetime in the same response that carried it. Absent for a token the caller supplied from elsewhere - that one arrived with no accompanying statement, and this SDK does not read a token's claims to find out.
- Specified by:
currentTokenExpiryin interfaceTokenSource- Returns:
- the expiry, or
nullwhen nothing is cached or the lifetime is not known
-
cachedExpiry
The instant the currently cached token expires.- Returns:
- the expiry, or
nullwhen nothing is cached
-
hasCachedToken
public final boolean hasCachedToken()Whether a token is currently cached. Reveals no token value.- Returns:
truewhen a token is held
-