5.1.1 — Post-login redirect hardening
This release hardens post-login redirect handling so browser auto-probes (e.g., Safari's /apple-touch-icon.png) can no longer hijack where users land after authentication, and it auto-unprotects common probe paths so they no longer bounce unauthenticated users to the login page. No endpoints, response-code contracts, or user.* properties changed.
SemVer classification: patch — security hardening and small defaults-only behavior adjustments; no new public API and no source/binary-incompatible changes for consuming applications.
Installation
Gradle:
implementation 'com.digitalsanctuary:ds-spring-user-framework:5.1.1'Maven:
<dependency>
<groupId>com.digitalsanctuary</groupId>
<artifactId>ds-spring-user-framework</artifactId>
<version>5.1.1</version>
</dependency>Built and verified against Spring Boot 4.1.0 (compatible with 4.0.x), Java 21+.
Security
- Hardened, consumer-overridable
RequestCacheto prevent post-login redirect hijacking:- A new default
RequestCachebean is contributed byUserSecurityBeansAutoConfiguration(@ConditionalOnMissingBean). It usesHttpSessionRequestCachewith a stricter matcher that saves only plausible user navigations:- GET requests that explicitly accept
text/html(Accept: */*is ignored — real navigations listtext/html). - Not XHR (
X-Requested-With: XMLHttpRequest), not HTMX (HX-Request), and not static-asset/auto-probe paths. - Excludes well-known probe/static paths:
/apple-touch-icon*,/favicon*,/.well-known/, and common static file extensions (png, ico, css, js, fonts, etc.).
- GET requests that explicitly accept
WebSecurityConfignow wires thatRequestCacheintohttp.requestCache(...), andLoginSuccessServicereads from the same injectedRequestCache, ensuring the save and read sides are consistent — so the user is redirected back to the original protected page they clicked, not to an auto-probed icon URL.- Consumer impact:
- If you want Spring Security's default (save-anything) semantics, define your own
RequestCachebean; the framework will back off and use yours in both the filter chain andLoginSuccessService. - If you previously relied on saving non-HTML, XHR/HTMX, or asset fetches to the saved request, explicitly override the
RequestCacheto restore that behavior.
- If you want Spring Security's default (save-anything) semantics, define your own
- A new default
Behavior changes (client impact)
getUnprotectedURIsList()now auto-unprotects browser/crawler probe paths for every consumer:/apple-touch-icon*.png,/favicon.*, and/.well-known/**are added to thepermitAlllist by default (least-privilege patterns, narrowly scoped).- Under
user.security.defaultAction=deny, unauthenticated requests to these probe URLs will now reach your app (typically 200 if you serve the asset, or 404 if you don't) instead of 302-redirecting to the login page. This reduces noisy, misleading login redirects triggered by the browser while the login page is rendering. - If your application intentionally kept any of these paths protected, define your own
SecurityFilterChainbean to fully control thepermitAllsurface.
Fixes
- Post-login redirect correctness:
SavedRequestAwareAuthenticationSuccessHandler(viaLoginSuccessService) now reads from the sameRequestCacheinstance the filter chain writes to, so a consumer override is honored end-to-end and the redirect target can't be silently lost or replaced by a browser probe.
Testing
- Added
RequestCacheHardeningTestproving that an HTML navigation survives an/apple-touch-icon.pngprobe on the same session, and that probes/partials (HTMX/XHR), non-HTML requests, and POSTs are not saved. - Added
WebSecurityAuthorizationDenyTestcoverage for the new auto-unprotected probe paths (asserting 404 rather than a 302-to-login when they aren't listed in configuration). - Stabilized parallel tests: introduced a thread-local
StatementCountInspectorto isolate N+1 query-count assertions from concurrent tests, and de-flaked a warning-count assertion inUserAPIUnitTest.
Other Changes
build.gradle: bumpedorg.hibernate.validator:hibernate-validatorto9.1.2.Finalfor tests.- Release tooling:
generate_changelog.pyhardened (never blocks a release); no impact on runtime behavior for consumers.
Full Changelog: 5.1.0...5.1.1