Session fixation, concurrency and remember-me

Spring Security · lesson 29 of 31 · 6 min read

The session controls that matter, and the cookie flags that do the real work.

Open this lesson in the learning hub

Key points

  • Session fixation: an attacker sets a known session id on the victim before login. Unless the id is rotated at authentication, they now share the authenticated session.
  • Spring Security rotates by default with changeSessionId(). Disabling it, or building a custom login that does not rotate, reintroduces the vulnerability.
  • Cookie flags are the highest-value, lowest-effort control. HttpOnly blocks JavaScript access, Secure forbids plaintext transmission, and SameSite=Lax removes most CSRF exposure at the browser.
  • Concurrent session control limits how many live sessions one account may have. It requires session state the server can see, so in a clustered deployment it means Spring Session backed by Redis rather than in-memory.
  • remember-me is a long-lived credential in a cookie. The persistent-token variant, which rotates the token on each use and detects reuse of an old one, is far safer than the hash-based one.
  • Logout must invalidate server-side. Clearing the cookie alone leaves the session valid, so anyone holding the old id - from a log, a proxy or a shared machine - remains signed in.

Example

http
    .sessionManagement(s -> s
            // Rotate the id at login. This is the fixation defence.
            .sessionFixation(f -> f.changeSessionId())
            // One live session per account; the older one is expired.
            .maximumSessions(1)
            .maxSessionsPreventsLogin(false)
            .expiredUrl("/login?expired"))

    .logout(l -> l
            .invalidateHttpSession(true)      // server-side, not just the cookie
            .deleteCookies("JSESSIONID")
            .clearAuthentication(true))

    // Persistent tokens: rotated per use, and reuse of an old token is
    // treated as theft and invalidates the series.
    .rememberMe(r -> r
            .tokenRepository(persistentTokenRepository())
            .tokenValiditySeconds(1209600)    // 14 days
            .useSecureCookie(true));

---

# The cookie flags do most of the work, and cost nothing.
server:
  servlet:
    session:
      timeout: 30m
      cookie:
        http-only: true      # JavaScript cannot read it -> XSS cannot steal it
        secure: true         # never sent over plain HTTP
        same-site: lax       # not sent on cross-site POSTs -> most CSRF gone

# Clustered? maximumSessions needs shared session state:
spring:
  session:
    store-type: redis        # in-memory counts only THIS instance sessions

# Attack, and what stops it:
#   fixation        -> changeSessionId() at login
#   XSS theft       -> HttpOnly
#   network sniff   -> Secure + HSTS
#   CSRF            -> SameSite=Lax, plus CSRF tokens
#   shared machine  -> server-side invalidation on logout

Rotate the session id at login and set HttpOnly, Secure and SameSite - those four together remove most session attacks.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Security course, and every lesson in it is listed on the Spring Security contents page.