From b6affdb2a35c6066cb1e915e8d1f1c34218b4bee Mon Sep 17 00:00:00 2001 From: skdas20 Date: Fri, 14 Aug 2026 15:57:53 +0000 Subject: [PATCH] Fail fast when UserDetailsService returns null onLoginSuccess dereferenced the result of loadUserByUsername without a null check, so a UserDetailsService returning null instead of throwing UsernameNotFoundException surfaced as a bare NullPointerException from inside the remember-me filter. processAutoLoginCookie already guards the same call and treats a null return as an interface contract violation. Apply the same guard to onLoginSuccess so both lookups report the misconfiguration consistently. Closes gh-19535 Signed-off-by: skdas20 --- .../rememberme/TokenBasedRememberMeServices.java | 2 ++ .../TokenBasedRememberMeServicesTests.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java index e970e9326e1..ece060698d9 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java @@ -225,6 +225,8 @@ public void onLoginSuccess(HttpServletRequest request, HttpServletResponse respo } if (!StringUtils.hasLength(password)) { UserDetails user = getUserDetailsService().loadUserByUsername(username); + Assert.notNull(user, () -> "UserDetailsService " + getUserDetailsService() + " returned null for username " + + username + ". " + "This is an interface contract violation"); password = user.getPassword(); if (!StringUtils.hasLength(password)) { this.logger.debug("Unable to obtain password for user: " + username); diff --git a/web/src/test/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServicesTests.java b/web/src/test/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServicesTests.java index 0de8ffd59f0..c1090320fa7 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServicesTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServicesTests.java @@ -361,6 +361,20 @@ public void loginSuccessIgnoredIfParameterNotSetOrFalse() { assertThat(cookie).isNull(); } + @Test + public void loginSuccessFailsIfUserServiceMisconfigured() { + udsWillReturnNull(); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter(AbstractRememberMeServices.DEFAULT_PARAMETER, "true"); + MockHttpServletResponse response = new MockHttpServletResponse(); + // the token carries no credentials, so the password is looked up via the + // UserDetailsService + Authentication authentication = new TestingAuthenticationToken("someone", null, "ROLE_ABC"); + assertThatIllegalArgumentException() + .isThrownBy(() -> this.services.loginSuccess(request, response, authentication)) + .withMessageContaining("interface contract violation"); + } + @Test public void loginSuccessNormalWithNonUserDetailsBasedPrincipalSetsExpectedCookie() { // SEC-822