Skip to content

feat: soft-refresh UserDetails via authz epoch and generation stamps - #24493

Open
netroms wants to merge 1 commit into
masterfrom
feat/userdetails-soft-refresh-v2
Open

feat: soft-refresh UserDetails via authz epoch and generation stamps#24493
netroms wants to merge 1 commit into
masterfrom
feat/userdetails-soft-refresh-v2

Conversation

@netroms

@netroms netroms commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace role/group/OU-driven mass SessionRegistry.expireNow with generation stamps in a new authz_version table (Flyway V2_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).
  • Freshness stamps (authzCheckedEpoch + authzGen) live on the immutable UserDetailsImpl snapshot itself, so sessions, JWT, and PAT converge on one three-way check in AuthzService.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.
  • SoftRefreshSecurityContextRepository decorates the HttpSession delegate 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.
  • 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 (guarded by UserDetailsImplSerializationTest).
  • Role authority/restriction edits and role deletion are O(1) (one role-gen bump) regardless of member count. User role/OU changes and group-membership deltas (import hooks + 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.
  • The store SQL is PostgreSQL-only, like production; feature coverage runs against Postgres (AuthzSoftRefreshHooksTest, JdbcAuthzVersionStoreTest). H2 test contexts wire a functional in-memory double in H2TestConfig.

Supersedes #24490 (v1 draft; same goal, reworked storage and refresh design per review).

Test plan

  • Unit: DefaultAuthzServiceTest (10, incl. stamp-before-build invariant, gen fast path, refreshIfStale three-way), InMemoryAuthzVersionStoreTest (5), SoftRefreshSecurityContextRepositoryTest (9), UserDetailsImplSerializationTest (3)
  • Postgres IT: 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, UserRoleTest
  • H2 web-api: UserControllerTest 52/52, MeControllerTest 28/28, ApiTokenAuthenticationTest 11/11, JwtBearerTokenTest 6/6
  • Live server (fresh DB, full Flyway boot): grant and revoke visible on the next request of the same session, JSESSIONID unchanged, epoch fast path 200, disable still hard-kicks (302)

AI Assisted

@netroms
netroms force-pushed the feat/userdetails-soft-refresh-v2 branch 3 times, most recently from 3e499ad to b65f4ae Compare July 18, 2026 21:22
@netroms
netroms force-pushed the feat/userdetails-soft-refresh-v2 branch from b65f4ae to a1636ab Compare July 18, 2026 22:22
@netroms
netroms marked this pull request as ready for review July 18, 2026 22:34
@netroms
netroms marked this pull request as draft July 18, 2026 22:35
@netroms
netroms force-pushed the feat/userdetails-soft-refresh-v2 branch from a1636ab to cf053d4 Compare July 19, 2026 00:23
@netroms
netroms marked this pull request as ready for review July 19, 2026 00:24
@netroms
netroms force-pushed the feat/userdetails-soft-refresh-v2 branch from cf053d4 to 5634d42 Compare July 19, 2026 08:17
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
netroms force-pushed the feat/userdetails-soft-refresh-v2 branch from 5634d42 to c4332af Compare July 19, 2026 08:29
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant