feat: soft-refresh UserDetails via authz epoch and generation stamps - #24493
Open
netroms wants to merge 1 commit into
Open
feat: soft-refresh UserDetails via authz epoch and generation stamps#24493netroms wants to merge 1 commit into
netroms wants to merge 1 commit into
Conversation
3 tasks
netroms
force-pushed
the
feat/userdetails-soft-refresh-v2
branch
3 times, most recently
from
July 18, 2026 21:22
3e499ad to
b65f4ae
Compare
7 tasks
netroms
force-pushed
the
feat/userdetails-soft-refresh-v2
branch
from
July 18, 2026 22:22
b65f4ae to
a1636ab
Compare
netroms
marked this pull request as ready for review
July 18, 2026 22:34
netroms
marked this pull request as draft
July 18, 2026 22:35
netroms
force-pushed
the
feat/userdetails-soft-refresh-v2
branch
from
July 19, 2026 00:23
a1636ab to
cf053d4
Compare
netroms
marked this pull request as ready for review
July 19, 2026 00:24
netroms
force-pushed
the
feat/userdetails-soft-refresh-v2
branch
from
July 19, 2026 08:17
cf053d4 to
5634d42
Compare
Replace role/user-driven mass SessionRegistry.expireNow with a JDBC-backed
two-level invalidation scheme:
- authz_version table (Flyway V2_44_17) holds per-user ('user', <uid>) and
per-role ('role', <uid>) generation stamps plus a single global epoch
row; every bump advances the epoch in the same transaction as the entity
change, so readers can never observe a stamp without its data. The store
SQL is PostgreSQL-only like production; H2 test contexts run against an
in-memory store wired in H2TestConfig, and feature coverage runs against
Postgres.
- Freshness stamps (checked epoch + effective gen) live on the immutable
UserDetailsImpl snapshot itself, so sessions, JWT, and PAT converge on
one three-way check in AuthzService.refreshIfStale: epoch fast path,
gen re-stamp path, rebuild path. Gen stamps are validated by a second
epoch read after the build; if the epoch moved mid-build the gen is
stamped 0 (unknown), failing safe.
- SoftRefreshSecurityContextRepository decorates the HttpSession delegate
of the standard security context repository pair: it extracts the
session principal, asks refreshIfStale, and persists the re-stamped or
rebuilt context. Decorating the repository makes the refresh
session-only by construction (JWT, PAT, and stateless authentications
never load through it), skips impersonated sessions, and defers the
check until a request actually accesses its authentication. No logout,
no cookie change.
- AuthzService memoizes stamped snapshots per username (stamp-before-build
invariant) with striped single-flight rebuilds; JWT and PAT share the
same cache and gain the gen fast path: bumps for unrelated users
re-stamp instead of rebuilding.
- UserDetailsImpl serialVersionUID is pinned to the pre-stamp shape, so
sessions serialized by earlier releases deserialize with stamps 0 and
take one soft refresh instead of a forced re-login; no release-boundary
session drain is needed (guarded by UserDetailsImplSerializationTest).
- Role authority/restriction edits and role deletion bump one role key,
O(1) regardless of member count. User role/org-unit changes and group
membership deltas (import hook owning side + service paths) bump only
affected users.
- Password change, disable, lock, and expiry keep hard session
invalidation. PAT account-flag validation builds fresh at token
cache-miss since flag changes do not advance the epoch.
netroms
force-pushed
the
feat/userdetails-soft-refresh-v2
branch
from
July 19, 2026 08:29
5634d42 to
c4332af
Compare
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
SessionRegistry.expireNowwith generation stamps in a newauthz_versiontable (FlywayV2_44_17, registered in the WOW coordination doc): per-user and per-role gens plus one global epoch row, all bumped in the same transaction as the entity change (readers can never see a stamp without its data).authzCheckedEpoch+authzGen) live on the immutableUserDetailsImplsnapshot itself, so sessions, JWT, and PAT converge on one three-way check inAuthzService.refreshIfStale: epoch fast path (1 query), gen re-stamp path (unrelated bumps re-stamp instead of rebuilding), rebuild path. Gen stamps are validated by a second epoch read after the build; if the epoch moved mid-build the gen is stamped 0 (unknown), failing safe.SoftRefreshSecurityContextRepositorydecorates theHttpSessiondelegate of the standard security-context repository pair and only extracts, swaps, and persists. Decorating the repository makes the refresh session-only by construction (JWT/PAT/stateless never load through it), skips impersonated sessions, and defers the check until a request actually accesses its authentication. No logout, no cookie change, no session attributes.UserDetailsImplserialVersionUIDis pinned to the pre-stamp shape, so sessions serialized by earlier releases deserialize with stamps 0 and take one soft refresh instead of a forced re-login — no release-boundary session drain (guarded byUserDetailsImplSerializationTest).AuthzSoftRefreshHooksTest,JdbcAuthzVersionStoreTest). H2 test contexts wire a functional in-memory double inH2TestConfig.Supersedes #24490 (v1 draft; same goal, reworked storage and refresh design per review).
Test plan
DefaultAuthzServiceTest(10, incl. stamp-before-build invariant, gen fast path, refreshIfStale three-way),InMemoryAuthzVersionStoreTest(5),SoftRefreshSecurityContextRepositoryTest(9),UserDetailsImplSerializationTest(3)JdbcAuthzVersionStoreTest(7, real upsert SQL, hermetic),AuthzSoftRefreshHooksTest(4: group membership, role authority, role delete, user role assignment — through the real API against the production store),UserServiceTest,UserGroupServiceTest,UserRoleTestUserControllerTest52/52,MeControllerTest28/28,ApiTokenAuthenticationTest11/11,JwtBearerTokenTest6/6AI Assisted