Skip to content

Commit 08f7146

Browse files
franticticktickjzheaux
authored andcommitted
Add Support BadCredentialsException to OneTimeTokenAuthenticationProvider
Closes gh-16494 Signed-off-by: Max Batischev <[email protected]>
1 parent 7fc5d50 commit 08f7146

File tree

2 files changed

+132
-6
lines changed

2 files changed

+132
-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,119 @@
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+
import org.springframework.util.CollectionUtils;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
36+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
37+
import static org.mockito.ArgumentMatchers.any;
38+
import static org.mockito.ArgumentMatchers.anyString;
39+
import static org.mockito.BDDMockito.given;
40+
41+
/**
42+
* Tests for {@link OneTimeTokenAuthenticationProvider}.
43+
*
44+
* @author Max Batischev
45+
*/
46+
@ExtendWith(MockitoExtension.class)
47+
public class OneTimeTokenAuthenticationProviderTests {
48+
49+
private static final String TOKEN = "token";
50+
51+
private static final String USERNAME = "Max";
52+
53+
private static final String PASSWORD = "password";
54+
55+
@Mock
56+
private OneTimeTokenService oneTimeTokenService;
57+
58+
@Mock
59+
private UserDetailsService userDetailsService;
60+
61+
@InjectMocks
62+
private OneTimeTokenAuthenticationProvider provider;
63+
64+
@Test
65+
void authenticateWhenAuthenticationTokenIsPresentThenAuthenticates() {
66+
given(this.oneTimeTokenService.consume(any()))
67+
.willReturn(new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now().plusSeconds(120)));
68+
given(this.userDetailsService.loadUserByUsername(anyString()))
69+
.willReturn(new User(USERNAME, PASSWORD, List.of()));
70+
OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
71+
72+
OneTimeTokenAuthenticationToken authentication = (OneTimeTokenAuthenticationToken) this.provider
73+
.authenticate(token);
74+
75+
User user = (User) authentication.getPrincipal();
76+
assertThat(authentication.isAuthenticated()).isTrue();
77+
assertThat(user.getUsername()).isEqualTo(USERNAME);
78+
assertThat(user.getPassword()).isEqualTo(PASSWORD);
79+
assertThat(CollectionUtils.isEmpty(user.getAuthorities())).isTrue();
80+
}
81+
82+
@Test
83+
void authenticateWhenOneTimeTokenIsNotFoundThenFails() {
84+
given(this.oneTimeTokenService.consume(any())).willReturn(null);
85+
OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
86+
87+
assertThatExceptionOfType(InvalidOneTimeTokenException.class)
88+
.isThrownBy(() -> this.provider.authenticate(token));
89+
}
90+
91+
@Test
92+
void authenticateWhenUserIsNotFoundThenFails() {
93+
given(this.oneTimeTokenService.consume(any()))
94+
.willReturn(new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now().plusSeconds(120)));
95+
given(this.userDetailsService.loadUserByUsername(anyString())).willThrow(UsernameNotFoundException.class);
96+
OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
97+
98+
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.provider.authenticate(token));
99+
}
100+
101+
@Test
102+
void constructorWhenOneTimeTokenServiceIsNullThenThrowIllegalArgumentException() {
103+
// @formatter:off
104+
assertThatIllegalArgumentException()
105+
.isThrownBy(() -> new OneTimeTokenAuthenticationProvider(null, this.userDetailsService))
106+
.withMessage("oneTimeTokenService cannot be null");
107+
// @formatter:on
108+
}
109+
110+
@Test
111+
void constructorWhenUserDetailsServiceIsNullThenThrowIllegalArgumentException() {
112+
// @formatter:off
113+
assertThatIllegalArgumentException()
114+
.isThrownBy(() -> new OneTimeTokenAuthenticationProvider(this.oneTimeTokenService, null))
115+
.withMessage("userDetailsService cannot be null");
116+
// @formatter:on
117+
}
118+
119+
}

0 commit comments

Comments
 (0)