You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
user.security.rememberMe.enabled exists and is wired into the filter chain, but the surrounding support is missing to the point that enabling it does nothing. There is no persistent token store, no cookie configuration, no documentation, no test coverage, and no remember-me field in the reference login form — so a consumer who sets the flag gets no cookie and no error telling them why.
This proposes completing it. The security interaction in step 3 is the substantive part; the rest is wiring.
No PersistentTokenRepository or RememberMeServices bean anywhere in src/ (verified by grep). Passing only key + userDetailsService leaves Spring's hash-based TokenBasedRememberMeServices.
No persistent_logins table in db-scripts/, so the JDBC token-repository path isn't provisioned either way.
No cookie configuration. Only two properties exist (enabled, key) — no token validity, secure-cookie flag, cookie name, or request-parameter name.
Undocumented. Absent from src/main/resources/config/dsspringuserconfig.properties and from CONFIG.md. The only traces are additional-spring-configuration-metadata.json:308-316 and a bare README.md bullet under Local Authentication.
Zero test coverage.grep -rn "rememberMe\|RememberMe" src/test returns nothing.
Unreachable via the reference login form. The demo app's templates/user/login.html posts only username and password. With no remember-me parameter and no alwaysRemember, AbstractRememberMeServices never issues a cookie. Enabling the flag today is a no-op for the documented login path. (Form change tracked separately: Add remember-me checkbox to the reference login form SpringUserFrameworkDemoApp#79.)
Why this is worth doing here
DSUserDetails implements UserDetails, OAuth2User, and OidcUser simultaneously (service/DSUserDetails.java:43), and every authentication path converges on it — form login, OAuth2, OIDC, and WebAuthn. That means a RememberMeAuthenticationToken carrying a DSUserDetails principal is type-compatible with every @AuthenticationPrincipal DSUserDetails controller signature in the library. The usual blocker for adding remember-me to an OAuth2-flavoured app — that remember-me mints a different principal type than the OAuth2 filters do — does not exist in this codebase.
Two known behavioural caveats, both acceptable and worth documenting rather than fixing:
On a remember-me login, getIdToken() / getUserInfo() return null and getAttributes() falls back to the map synthesized from the User entity (DSUserDetails.java:153-178, 269-282). A consumer reading id-token claims degrades gracefully but loses provider claims.
AuthenticationEventListener.java:59-62 has a principal instanceof String branch commented "Basic authentication or remember-me". That branch is unreachable for this codebase's remember-me, which lands in the DSUserDetails branch. Stale comment, no functional impact.
Add four properties, defaulting to Spring Security's own defaults:
Property
Default
user.security.rememberMe.tokenValiditySeconds
1209600 (14 days)
user.security.rememberMe.rememberMeParameter
remember-me
user.security.rememberMe.rememberMeCookieName
remember-me
user.security.rememberMe.useSecureCookie
unset — defer to Spring's behaviour (secure flag set when the request is HTTPS)
Inject an optional PersistentTokenRepository and call .tokenRepository(...) when one is present, so consumers can choose hash-based or persistent without editing the chain.
2. Optional persistent token store — security/UserSecurityBeansAutoConfiguration.java + db-scripts/
A @ConditionalOnMissingBean(PersistentTokenRepository.class)JdbcTokenRepositoryImpl, gated on a property so it is never created without the table existing. Add persistent_logins to the schema scripts.
3. Token revocation on session invalidation
SessionInvalidationService.invalidateUserSessions (line 67) and invalidateSessionsAfterPasswordChange (line 127) both work purely through SessionInformation.expireNow() (lines 93, 156). Neither touches remember-me state, because there is none today.
Decided semantics, by token type:
Persistent tokens (JDBC store): both invalidateUserSessions and invalidateSessionsAfterPasswordChange call PersistentTokenRepository.removeUserTokens(username) when a PersistentTokenRepository bean is present (inject it as optional). This must land in the same change as step 2, because TokenBasedRememberMeServices signs the cookie with the password hash — persistent tokens lose that incidental password-change protection, so shipping the store without revocation would be a regression relative to the hash-based default.
Hash-based tokens (default): admin-initiated revocation ("sign this user out everywhere") is not implementable — TokenBasedRememberMeServices keeps no server-side state; the cookie is a self-contained signature over username, expiry, password hash, and key. Nothing to delete. Password change already invalidates these cookies (signature mismatch). Document this as an explicit limitation of hash-based mode: consumers who need admin revocation must use the persistent store. Do not attempt a workaround (key rotation would log out all users).
Worth checking against the fix in #329, which addressed this class of gap for sessions.
To be precise about what is not affected: disabled and locked accounts are already rejected on remember-me auto-login, because AbstractRememberMeServices runs an AccountStatusUserDetailsChecker and DSUserDetails.isEnabled() / isAccountNonLocked() return real values from the User entity (DSUserDetails.java:226-228, 246-248). This is not a lockout-bypass issue.
Document the properties with defaults, the persistent_logins requirement when using the JDBC store, the hash-based admin-revocation limitation from step 3, and — most importantly — that the consumer's login form must post the remember-me parameter. The current bare README bullet implies the feature works out of the box.
5. Tests — currently none
Cookie issuance on login with the parameter present.
Cookie-based re-authentication producing a DSUserDetails principal.
SessionInvalidationService calls removeUserTokens for persistent tokens on both invalidation paths (and is a no-op without a PersistentTokenRepository bean).
Hash-based cookie is rejected after a password change (signature mismatch) — this is the only revocation guarantee for hash-based mode; there is deliberately no test for admin revocation of hash-based tokens, per step 3.
A remember-me login still populates session profiles via InteractiveAuthenticationSuccessEvent — BaseAuthenticationListener listens on that event (line 41) and Spring's RememberMeAuthenticationFilter publishes it, but nothing in this repo verifies it.
No issue in this repo, open or closed, reports idle logout or asks for a stay-signed-in option. This is a latent gap in a half-wired feature rather than a response to reported pain — reasonable to schedule accordingly. The argument for doing it as one unit rather than incrementally is step 3: the persistent token store and its revocation path should not ship in separate releases.
Summary
user.security.rememberMe.enabledexists and is wired into the filter chain, but the surrounding support is missing to the point that enabling it does nothing. There is no persistent token store, no cookie configuration, no documentation, no test coverage, and noremember-mefield in the reference login form — so a consumer who sets the flag gets no cookie and no error telling them why.This proposes completing it. The security interaction in step 3 is the substantive part; the rest is wiring.
Current state
Wired in
security/WebSecurityConfig.java:What's missing:
PersistentTokenRepositoryorRememberMeServicesbean anywhere insrc/(verified by grep). Passing onlykey+userDetailsServiceleaves Spring's hash-basedTokenBasedRememberMeServices.persistent_loginstable indb-scripts/, so the JDBC token-repository path isn't provisioned either way.enabled,key) — no token validity, secure-cookie flag, cookie name, or request-parameter name.src/main/resources/config/dsspringuserconfig.propertiesand fromCONFIG.md. The only traces areadditional-spring-configuration-metadata.json:308-316and a bareREADME.mdbullet under Local Authentication.grep -rn "rememberMe\|RememberMe" src/testreturns nothing.templates/user/login.htmlposts onlyusernameandpassword. With noremember-meparameter and noalwaysRemember,AbstractRememberMeServicesnever issues a cookie. Enabling the flag today is a no-op for the documented login path. (Form change tracked separately: Add remember-me checkbox to the reference login form SpringUserFrameworkDemoApp#79.)Why this is worth doing here
DSUserDetailsimplementsUserDetails,OAuth2User, andOidcUsersimultaneously (service/DSUserDetails.java:43), and every authentication path converges on it — form login, OAuth2, OIDC, and WebAuthn. That means aRememberMeAuthenticationTokencarrying aDSUserDetailsprincipal is type-compatible with every@AuthenticationPrincipal DSUserDetailscontroller signature in the library. The usual blocker for adding remember-me to an OAuth2-flavoured app — that remember-me mints a different principal type than the OAuth2 filters do — does not exist in this codebase.Two known behavioural caveats, both acceptable and worth documenting rather than fixing:
getIdToken()/getUserInfo()returnnullandgetAttributes()falls back to the map synthesized from theUserentity (DSUserDetails.java:153-178,269-282). A consumer reading id-token claims degrades gracefully but loses provider claims.AuthenticationEventListener.java:59-62has aprincipal instanceof Stringbranch commented "Basic authentication or remember-me". That branch is unreachable for this codebase's remember-me, which lands in theDSUserDetailsbranch. Stale comment, no functional impact.Proposed work
1. Configuration surface —
security/WebSecurityConfig.java:101-105, 155-156Add four properties, defaulting to Spring Security's own defaults:
user.security.rememberMe.tokenValiditySeconds1209600(14 days)user.security.rememberMe.rememberMeParameterremember-meuser.security.rememberMe.rememberMeCookieNameremember-meuser.security.rememberMe.useSecureCookieInject an optional
PersistentTokenRepositoryand call.tokenRepository(...)when one is present, so consumers can choose hash-based or persistent without editing the chain.2. Optional persistent token store —
security/UserSecurityBeansAutoConfiguration.java+db-scripts/A
@ConditionalOnMissingBean(PersistentTokenRepository.class)JdbcTokenRepositoryImpl, gated on a property so it is never created without the table existing. Addpersistent_loginsto the schema scripts.3. Token revocation on session invalidation
SessionInvalidationService.invalidateUserSessions(line 67) andinvalidateSessionsAfterPasswordChange(line 127) both work purely throughSessionInformation.expireNow()(lines 93, 156). Neither touches remember-me state, because there is none today.Decided semantics, by token type:
invalidateUserSessionsandinvalidateSessionsAfterPasswordChangecallPersistentTokenRepository.removeUserTokens(username)when aPersistentTokenRepositorybean is present (inject it as optional). This must land in the same change as step 2, becauseTokenBasedRememberMeServicessigns the cookie with the password hash — persistent tokens lose that incidental password-change protection, so shipping the store without revocation would be a regression relative to the hash-based default.TokenBasedRememberMeServiceskeeps no server-side state; the cookie is a self-contained signature over username, expiry, password hash, and key. Nothing to delete. Password change already invalidates these cookies (signature mismatch). Document this as an explicit limitation of hash-based mode: consumers who need admin revocation must use the persistent store. Do not attempt a workaround (key rotation would log out all users).Worth checking against the fix in #329, which addressed this class of gap for sessions.
To be precise about what is not affected: disabled and locked accounts are already rejected on remember-me auto-login, because
AbstractRememberMeServicesruns anAccountStatusUserDetailsCheckerandDSUserDetails.isEnabled()/isAccountNonLocked()return real values from theUserentity (DSUserDetails.java:226-228, 246-248). This is not a lockout-bypass issue.4. Documentation —
dsspringuserconfig.properties,CONFIG.md,README.mdDocument the properties with defaults, the
persistent_loginsrequirement when using the JDBC store, the hash-based admin-revocation limitation from step 3, and — most importantly — that the consumer's login form must post theremember-meparameter. The current bare README bullet implies the feature works out of the box.5. Tests — currently none
DSUserDetailsprincipal.SessionInvalidationServicecallsremoveUserTokensfor persistent tokens on both invalidation paths (and is a no-op without aPersistentTokenRepositorybean).InteractiveAuthenticationSuccessEvent—BaseAuthenticationListenerlistens on that event (line 41) and Spring'sRememberMeAuthenticationFilterpublishes it, but nothing in this repo verifies it.Out of scope
The reference login form checkbox lives in the demo app and is tracked as devondragon/SpringUserFrameworkDemoApp#79.
Note on priority
No issue in this repo, open or closed, reports idle logout or asks for a stay-signed-in option. This is a latent gap in a half-wired feature rather than a response to reported pain — reasonable to schedule accordingly. The argument for doing it as one unit rather than incrementally is step 3: the persistent token store and its revocation path should not ship in separate releases.