Browse Source

Add Support BadCredentialsException to OneTimeTokenAuthenticationProvider

Closes gh-16494

Signed-off-by: Max Batischev <mblancer@mail.ru>
Max Batischev 6 months ago
parent
commit
08f71461b4

+ 13 - 6
core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,10 +17,12 @@
 package org.springframework.security.authentication.ott;
 
 import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.util.Assert;
 
 /**
@@ -52,11 +54,16 @@ public final class OneTimeTokenAuthenticationProvider implements AuthenticationP
 		if (consumed == null) {
 			throw new InvalidOneTimeTokenException("Invalid token");
 		}
-		UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
-		OneTimeTokenAuthenticationToken authenticated = OneTimeTokenAuthenticationToken.authenticated(user,
-				user.getAuthorities());
-		authenticated.setDetails(otpAuthenticationToken.getDetails());
-		return authenticated;
+		try {
+			UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
+			OneTimeTokenAuthenticationToken authenticated = OneTimeTokenAuthenticationToken.authenticated(user,
+					user.getAuthorities());
+			authenticated.setDetails(otpAuthenticationToken.getDetails());
+			return authenticated;
+		}
+		catch (UsernameNotFoundException ex) {
+			throw new BadCredentialsException("Authentication failed.");
+		}
 	}
 
 	@Override

+ 119 - 0
core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProviderTests.java

@@ -0,0 +1,119 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.authentication.ott;
+
+import java.time.Instant;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.util.CollectionUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.BDDMockito.given;
+
+/**
+ * Tests for {@link OneTimeTokenAuthenticationProvider}.
+ *
+ * @author Max Batischev
+ */
+@ExtendWith(MockitoExtension.class)
+public class OneTimeTokenAuthenticationProviderTests {
+
+	private static final String TOKEN = "token";
+
+	private static final String USERNAME = "Max";
+
+	private static final String PASSWORD = "password";
+
+	@Mock
+	private OneTimeTokenService oneTimeTokenService;
+
+	@Mock
+	private UserDetailsService userDetailsService;
+
+	@InjectMocks
+	private OneTimeTokenAuthenticationProvider provider;
+
+	@Test
+	void authenticateWhenAuthenticationTokenIsPresentThenAuthenticates() {
+		given(this.oneTimeTokenService.consume(any()))
+			.willReturn(new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now().plusSeconds(120)));
+		given(this.userDetailsService.loadUserByUsername(anyString()))
+			.willReturn(new User(USERNAME, PASSWORD, List.of()));
+		OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
+
+		OneTimeTokenAuthenticationToken authentication = (OneTimeTokenAuthenticationToken) this.provider
+			.authenticate(token);
+
+		User user = (User) authentication.getPrincipal();
+		assertThat(authentication.isAuthenticated()).isTrue();
+		assertThat(user.getUsername()).isEqualTo(USERNAME);
+		assertThat(user.getPassword()).isEqualTo(PASSWORD);
+		assertThat(CollectionUtils.isEmpty(user.getAuthorities())).isTrue();
+	}
+
+	@Test
+	void authenticateWhenOneTimeTokenIsNotFoundThenFails() {
+		given(this.oneTimeTokenService.consume(any())).willReturn(null);
+		OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
+
+		assertThatExceptionOfType(InvalidOneTimeTokenException.class)
+			.isThrownBy(() -> this.provider.authenticate(token));
+	}
+
+	@Test
+	void authenticateWhenUserIsNotFoundThenFails() {
+		given(this.oneTimeTokenService.consume(any()))
+			.willReturn(new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now().plusSeconds(120)));
+		given(this.userDetailsService.loadUserByUsername(anyString())).willThrow(UsernameNotFoundException.class);
+		OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
+
+		assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.provider.authenticate(token));
+	}
+
+	@Test
+	void constructorWhenOneTimeTokenServiceIsNullThenThrowIllegalArgumentException() {
+		// @formatter:off
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> new OneTimeTokenAuthenticationProvider(null, this.userDetailsService))
+				.withMessage("oneTimeTokenService cannot be null");
+		// @formatter:on
+	}
+
+	@Test
+	void constructorWhenUserDetailsServiceIsNullThenThrowIllegalArgumentException() {
+		// @formatter:off
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> new OneTimeTokenAuthenticationProvider(this.oneTimeTokenService, null))
+				.withMessage("userDetailsService cannot be null");
+		// @formatter:on
+	}
+
+}