Skip to content

feat(vaadin-spring): add Vaadin-aware expired session handling - #25625

Open
totally-not-ai[bot] wants to merge 2 commits into
mainfrom
feat/uidl-expired-session-strategy
Open

feat(vaadin-spring): add Vaadin-aware expired session handling#25625
totally-not-ai[bot] wants to merge 2 commits into
mainfrom
feat/uidl-expired-session-strategy

Conversation

@totally-not-ai

Copy link
Copy Markdown
Contributor

new feature · vaadin-spring · apps that limit concurrent sessions

Background — session concurrency control. Spring Security can limit how
many sessions one user has at the same time. When the limit is passed, the
oldest session is marked as expired, and the next request from that browser
is answered by the configured expired-session strategy.

A Vaadin application that turns this on got a plain redirect, which the
Vaadin client cannot follow, so the browser stayed on a dead page until the
user reloaded by hand. The client now gets the token it understands and
reloads on its own. Applications that do not limit concurrent sessions are
not affected.

Risks:

  • ⚠️ Public API: one new class, two new methods on VaadinSecurityConfigurer;
    no existing signature changed
  • ⚠️ Behavior change: an app with concurrency control now gets the Vaadin
    strategy instead of the Spring Security redirect one, which is what such an
    app wants; enableSessionManagementConfiguration(false) keeps the old
    behavior
  • 🔒 Security: only changes what is written to the response after Spring
    Security has already expired the session and logged the user out
  • ✅ Clean otherwise: no migration, no threading, no serialized state, and no
    cost when concurrency control is off

Context. SSO Kit has carried UidlExpiredSessionStrategy since 1.0
because every Vaadin app with concurrency control needs it, and Flow already
has its sibling UidlRedirectStrategy. Keeping the two together lets SSO Kit
delegate instead of shipping its own copy.

  • Added UidlExpiredSessionStrategy, ported from SSO Kit: it writes
    Vaadin-Refresh: <url> into the body of a framework internal request and
    redirects any other request.
    • The destination URL is context-relative and the redirect goes through a
      RedirectStrategy, so an app under a context path gets /app/ where the
      SSO Kit original produced //app.
  • Made VaadinSecurityConfigurer install the strategy by default. It only
    touches session management when the application has it configured, which
    Spring Boot does, so a hand-built filter chain without session management
    is left alone.
  • Added enableSessionManagementConfiguration(boolean) to turn that off, and
    expiredSessionStrategy(SessionInformationExpiredStrategy) to set a custom
    strategy.
  • Added unit tests for the strategy and for the three configurer paths, and
    excluded the new class from SpringClassesSerializableTest the same way
    UidlRedirectStrategy is.

Port UidlExpiredSessionStrategy from SSO Kit so that a session expired by
Spring Security concurrency control ends in a client-side reload instead of
a redirect the Vaadin client cannot follow: for framework internal requests
it writes a Vaadin-Refresh token into the response body, other requests are
redirected.

Unlike the SSO Kit original, the destination URL is context-relative and the
redirect goes through a RedirectStrategy, so an application deployed under a
context path gets /app/ instead of //app.
VaadinSecurityConfigurer now installs UidlExpiredSessionStrategy on the
session management configuration, so an application that turns on Spring
Security concurrency control gets Vaadin-aware session expiration without
extra wiring. Spring Security only creates the ConcurrentSessionFilter when
a maximum number of sessions is set, so this is a no-op otherwise.

Session management is only customized when the application has it
configured, which Spring Boot does by default, and can be turned off with
enableSessionManagementConfiguration(false). A custom strategy can be set
with expiredSessionStrategy(SessionInformationExpiredStrategy).
@totally-not-ai

Copy link
Copy Markdown
Contributor Author

Type of change

  • Feature

How to test

  1. In flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java,
    add http.sessionManagement(s -> s.sessionConcurrency(c -> c.maximumSessions(1)))
    to the filter chain.
  2. Start the app and log in as john in one browser.
  3. Log in as john in a second browser (or a private window).
  4. Go back to the first browser and click a link in the app: the page reloads
    and lands on the login view, instead of staying on a page that no longer
    answers.
API changes

com.vaadin.flow.spring.security.UidlExpiredSessionStrategy

// Added
public class UidlExpiredSessionStrategy implements SessionInformationExpiredStrategy
public UidlExpiredSessionStrategy() // sends the browser to the context root
public UidlExpiredSessionStrategy(String destinationUrl) // context-relative URL
public void setRedirectStrategy(RedirectStrategy redirectStrategy) // used for non-UIDL requests
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException

com.vaadin.flow.spring.security.VaadinSecurityConfigurer

// Added
public VaadinSecurityConfigurer enableSessionManagementConfiguration(boolean enableSessionManagementConfiguration)
public VaadinSecurityConfigurer expiredSessionStrategy(SessionInformationExpiredStrategy expiredSessionStrategy)
Test coverage
  • UidlExpiredSessionStrategyTest: refresh token for a UIDL request at the
    context root, the same with a /app context path, a custom destination
    URL, and the redirect branch for a non-UIDL request.
  • VaadinSecurityConfigurerTest: with concurrency control on, an expired
    UIDL request gets Vaadin-Refresh: /; a strategy passed to
    expiredSessionStrategy(...) is used instead; and with
    enableSessionManagementConfiguration(false) no refresh token is written.
  • mvn -pl vaadin-spring test: 468 tests, all green except the pre-existing
    VaadinServletContextInitializerTest unicode-path failure, which also
    fails on main in this environment.
Follow-ups
  • A Vaadin-aware InvalidSessionStrategy would cover a plain session
    timeout; this PR only covers a session expired by concurrency control.
  • Hilla endpoint requests still get a redirect where a 401 would fit the
    client better.
  • SSO Kit can delegate to this class once it targets a Vaadin version that
    has it.

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

 1 441 files  +1   1 525 suites  +1   1h 36m 47s ⏱️ + 3m 8s
12 042 tests +7  11 974 ✅ +7  68 💤 ±0  0 ❌ ±0 
12 360 runs  +7  12 292 ✅ +7  68 💤 ±0  0 ❌ ±0 

Results for commit 53cfe19. ± Comparison against base commit 7cc33c0.

totally-not-ai Bot added a commit to vaadin/docs that referenced this pull request Sep 10, 2026
…ions

vaadin/flow#25625 adds UidlExpiredSessionStrategy and makes
VaadinSecurityConfigurer install it by default, and vaadin/flow#25627 adds
KeycloakOidcUserMapper behind a keycloakRoleMapping() opt-in. Both were
ported from SSO Kit, so two of the migration gaps close.

Moves the two features out of the gaps section and into the migration steps
that need them, with a since badge for the version they arrive in and the
previous manual approach kept in a note for earlier versions. Updates the
feature mapping table and the checklist to match.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants