Skip to content

Commit f178331

Browse files
Add Support BadCredentialsException to OneTimeTokenAuthenticationProvider
Closes gh-16494 Signed-off-by: Max Batischev <[email protected]>
1 parent cb16f48 commit f178331

File tree

2 files changed

+130
-6
lines changed

2 files changed

+130
-6
lines changed

core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,10 +17,12 @@
1717
package org.springframework.security.authentication.ott;
1818

1919
import org.springframework.security.authentication.AuthenticationProvider;
20+
import org.springframework.security.authentication.BadCredentialsException;
2021
import org.springframework.security.core.Authentication;
2122
import org.springframework.security.core.AuthenticationException;
2223
import org.springframework.security.core.userdetails.UserDetails;
2324
import org.springframework.security.core.userdetails.UserDetailsService;
25+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
2426
import org.springframework.util.Assert;
2527

2628
/**
@@ -52,11 +54,16 @@ public Authentication authenticate(Authentication authentication) throws Authent
5254
if (consumed == null) {
5355
throw new InvalidOneTimeTokenException("Invalid token");
5456
}
55-
UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
56-
OneTimeTokenAuthenticationToken authenticated = OneTimeTokenAuthenticationToken.authenticated(user,
57-
user.getAuthorities());
58-
authenticated.setDetails(otpAuthenticationToken.getDetails());
59-
return authenticated;
57+
try {
58+
UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
59+
OneTimeTokenAuthenticationToken authenticated = OneTimeTokenAuthenticationToken.authenticated(user,
60+
user.getAuthorities());
61+
authenticated.setDetails(otpAuthenticationToken.getDetails());
62+
return authenticated;
63+
}
64+
catch (UsernameNotFoundException ex) {
65+
throw new BadCredentialsException("Authentication failed.");
66+
}
6067
}
6168

6269
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.authentication.ott;
18+
19+
import java.time.Instant;
20+
import java.util.List;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
import org.mockito.InjectMocks;
25+
import org.mockito.Mock;
26+
import org.mockito.junit.jupiter.MockitoExtension;
27+
28+
import org.springframework.security.authentication.BadCredentialsException;
29+
import org.springframework.security.core.userdetails.User;
30+
import org.springframework.security.core.userdetails.UserDetailsService;
31+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
32+
33+
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
35+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
36+
import static org.mockito.ArgumentMatchers.any;
37+
import static org.mockito.ArgumentMatchers.anyString;
38+
import static org.mockito.BDDMockito.given;
39+
40+
/**
41+
* Tests for {@link OneTimeTokenAuthenticationProvider}.
42+
*
43+
* @author Max Batischev
44+
*/
45+
@ExtendWith(MockitoExtension.class)
46+
public class OneTimeTokenAuthenticationProviderTests {
47+
48+
private static final String TOKEN = "token";
49+
50+
private static final String USERNAME = "Max";
51+
52+
private static final String PASSWORD = "password";
53+
54+
@Mock
55+
private OneTimeTokenService oneTimeTokenService;
56+
57+
@Mock
58+
private UserDetailsService userDetailsService;
59+
60+
@InjectMocks
61+
private OneTimeTokenAuthenticationProvider provider;
62+
63+
@Test
64+
void authenticateWhenAuthenticationTokenIsPresentThenAuthenticates() {
65+
given(this.oneTimeTokenService.consume(any()))
66+
.willReturn(new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now().plusSeconds(120)));
67+
given(this.userDetailsService.loadUserByUsername(anyString()))
68+
.willReturn(new User(USERNAME, PASSWORD, List.of()));
69+
OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
70+
71+
OneTimeTokenAuthenticationToken authentication = (OneTimeTokenAuthenticationToken) this.provider
72+
.authenticate(token);
73+
74+
User user = (User) authentication.getPrincipal();
75+
assertThat(authentication.isAuthenticated()).isTrue();
76+
assertThat(user.getUsername()).isEqualTo(USERNAME);
77+
assertThat(user.getPassword()).isEqualTo(PASSWORD);
78+
}
79+
80+
@Test
81+
void authenticateWhenOneTimeTokenIsNotFoundThenFails() {
82+
given(this.oneTimeTokenService.consume(any())).willReturn(null);
83+
OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
84+
85+
assertThatExceptionOfType(InvalidOneTimeTokenException.class)
86+
.isThrownBy(() -> this.provider.authenticate(token));
87+
}
88+
89+
@Test
90+
void authenticateWhenUserIsNotFoundThenFails() {
91+
given(this.oneTimeTokenService.consume(any()))
92+
.willReturn(new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now().plusSeconds(120)));
93+
given(this.userDetailsService.loadUserByUsername(anyString())).willThrow(UsernameNotFoundException.class);
94+
OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
95+
96+
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.provider.authenticate(token));
97+
}
98+
99+
@Test
100+
void constructorWhenOneTimeTokenServiceIsNullThenThrowIllegalArgumentException() {
101+
// @formatter:off
102+
assertThatIllegalArgumentException()
103+
.isThrownBy(() -> new OneTimeTokenAuthenticationProvider(null, this.userDetailsService))
104+
.withMessage("oneTimeTokenService cannot be null");
105+
// @formatter:on
106+
}
107+
108+
@Test
109+
void constructorWhenUserDetailsServiceIsNullThenThrowIllegalArgumentException() {
110+
// @formatter:off
111+
assertThatIllegalArgumentException()
112+
.isThrownBy(() -> new OneTimeTokenAuthenticationProvider(this.oneTimeTokenService, null))
113+
.withMessage("userDetailsService cannot be null");
114+
// @formatter:on
115+
}
116+
117+
}

0 commit comments

Comments
 (0)