Class InMemoryLoginStateStore
- All Implemented Interfaces:
LoginStateStore
LoginStateStore: a single-use, TTL-bounded, size-bounded
in-memory store for the per-login state → LoginStateStore.PendingLogin association
created during OidcRelyingParty.startLogin(jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse) and consumed during OidcRelyingParty.completeLogin(jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse).
Security properties (see OpenID Connect Core §3.1.2.1 and the RP charter):
- Single-use —
consume(String)atomically removes the entry under a monitor, so a replayed callback with the samestatefails. Cross-node single-use requires a shared store with an atomic consume primitive (seeLoginStateStore.consume(String)); this impl only guarantees single-use within one JVM. - TTL-bounded — entries at or past their framework-minted
expiresAtare treated as a miss and swept. - Size-bounded — an LRU cap (default 10 000) prevents unbounded growth from abandoned
login attempts; eviction mirrors the
BoundedLruTokenCacheshape.
The framework mints createdAt/expiresAt on the LoginStateStore.PendingLogin record;
this impl persists them verbatim and does not stamp timestamps of its own. The ttl argument here
only bounds sweep/GC of abandoned entries and asserts the framework's (0, MAX_TTL] ceiling for the
default path; the relying party re-checks expiresAt independently on consume.
Thread-safe.
- Since:
- 10.0.0
-
Nested Class Summary
Nested classes/interfaces inherited from interface org.apache.juneau.rest.server.auth.oidc.rp.LoginStateStore
LoginStateStore.PendingLogin -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intDefault maximum number of in-flight login attempts retained.static final DurationDefault time-to-live for a pending login (the user has at most a few minutes at the IdP).Fields inherited from interface org.apache.juneau.rest.server.auth.oidc.rp.LoginStateStore
MAX_TTL -
Constructor Summary
ConstructorsConstructorDescriptionInMemoryLoginStateStore(Duration ttl, int maxEntries, Clock clock) Constructor. -
Method Summary
Modifier and TypeMethodDescriptionAtomically removes and returns the pending-login association forstate, if present.intsize()Returns the current entry count.voidstore(String state, LoginStateStore.PendingLogin pending) Stores a framework-built pending-login association keyed bystate.
-
Field Details
-
DEFAULT_MAX_ENTRIES
Default maximum number of in-flight login attempts retained.- See Also:
-
DEFAULT_TTL
Default time-to-live for a pending login (the user has at most a few minutes at the IdP).
-
-
Constructor Details
-
InMemoryLoginStateStore
Constructor.- Parameters:
ttl- The single-use TTL. Must be positive and not exceedLoginStateStore.MAX_TTL. Bounds sweep of abandoned entries and asserts the framework ceiling for the default path; per-entry expiry is driven by the framework-mintedLoginStateStore.PendingLogin.expiresAt().maxEntries- The LRU size cap. Must be positive.clock- The clock for TTL sweeping. Must not benull .
-
-
Method Details
-
store
Description copied from interface:LoginStateStoreStores a framework-built pending-login association keyed bystate.Implementations MUST persist the association durably enough that a subsequent
LoginStateStore.consume(String)on any node handling the callback can retrieve it. On an infrastructure failure (backend unreachable, write rejected) implementations MUST throw rather than silently drop the record:storeis invoked on thestartLoginleg before any authorization URL is issued, so a thrown exception fails the flow closed with no half-initiated login. Implementations MUST NOT log theLoginStateStore.PendingLoginpayload; wrap / sanitize backend exceptions so thecodeVerifier/statedo not leak into a 500 body or log.Last-writer-wins: a repeated
storefor the samestateoverwrites (a NimbusStatecollision is astronomical). A backend MAY useSET NXbut is not required to. A backend MAY use the remaining time toexpiresAtas a GC /EXPIREhint, but GC failure MUST NOT extend usability past the framework's re-checkedexpiresAt.- Specified by:
storein interfaceLoginStateStore- Parameters:
state- The opaquestatevalue. Must not benullor blank.pending- The framework-built association (already stamped withcreatedAt/expiresAt). Must not benull.
-
consume
Description copied from interface:LoginStateStoreAtomically removes and returns the pending-login association forstate, if present.Single-use / atomicity is mandatory (TOCTOU). The removal and the read MUST be a single atomic operation so a given
stateis consumed at most once. Of any two concurrent calls with the samestate, at most one returns a presentOptional; the other MUST miss — including across nodes sharing the store. Use an atomic primitive against the primary (RedisGETDELor a LuaGET+DEL), not a read-then-delete and not a possibly-stale replica read. A consume that removes the entry but fails to return it (lost response) MUST be treated as a miss on retry — never a best-effort put-back (put-back reopens replay); the user simply restarts login.Bounded I/O. Implementations MUST apply a bounded deadline to the backend round-trip so a hung backend cannot stall
completeLoginon a request thread indefinitely.empty()vs. throw — distinct meanings. ReturnOptional.empty()only for absent / already-consumed / (optionally) store-expired states — the caller treatsempty()as a CSRF / replay / expiry rejection. On an infrastructure failure (backend unreachable) the implementation MUST throw (not returnempty()) so the caller can distinguish an attack from an outage; the RP catches it, logs without the payload, and rejects with an "unavailable" authentication error. Either way the login fails closed; implementations MUST NEVER fabricate aLoginStateStore.PendingLogin.Expiry is not the store's sole responsibility. An implementation MAY treat past-
expiresAtentries as a miss (early eviction), but the RP re-checksexpiresAton the returned record regardless, so it is not a security defect if a store returns an over-age record — the framework rejects it.- Specified by:
consumein interfaceLoginStateStore- Parameters:
state- Thestatevalue from the callback. May benull(returnsempty(), matching the defensive callback path — do not throw).- Returns:
- The association, or
Optional.empty()if absent, expired, or already consumed.
-
size
Returns the current entry count. Primarily for tests and metrics.- Returns:
- The entry count.
-