Skip to content

Commit 4187af3

Browse files
Seol-JYjgrandja
authored andcommitted
Verify token deletion in JdbcOneTimeTokenService
1 parent a317a3d commit 4187af3

2 files changed

Lines changed: 31 additions & 3 deletions

File tree

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ public OneTimeToken consume(OneTimeTokenAuthenticationToken authenticationToken)
152152
return null;
153153
}
154154
OneTimeToken token = tokens.get(0);
155-
deleteOneTimeToken(token);
155+
if (deleteOneTimeToken(token) == 0) {
156+
return null;
157+
}
156158
if (isExpired(token)) {
157159
return null;
158160
}
@@ -170,11 +172,11 @@ private List<OneTimeToken> selectOneTimeToken(OneTimeTokenAuthenticationToken au
170172
return this.jdbcOperations.query(SELECT_ONE_TIME_TOKEN_SQL, pss, this.oneTimeTokenRowMapper);
171173
}
172174

173-
private void deleteOneTimeToken(OneTimeToken oneTimeToken) {
175+
private int deleteOneTimeToken(OneTimeToken oneTimeToken) {
174176
List<SqlParameterValue> parameters = List
175177
.of(new SqlParameterValue(Types.VARCHAR, oneTimeToken.getTokenValue()));
176178
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
177-
this.jdbcOperations.update(DELETE_ONE_TIME_TOKEN_SQL, pss);
179+
return this.jdbcOperations.update(DELETE_ONE_TIME_TOKEN_SQL, pss);
178180
}
179181

180182
private ThreadPoolTaskScheduler createTaskScheduler(String cleanupCron) {

core/src/test/java/org/springframework/security/authentication/ott/JdbcOneTimeTokenServiceTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,24 @@
2121
import java.time.Instant;
2222
import java.time.ZoneOffset;
2323
import java.time.temporal.ChronoUnit;
24+
import java.util.List;
2425

2526
import org.junit.jupiter.api.AfterEach;
2627
import org.junit.jupiter.api.BeforeEach;
2728
import org.junit.jupiter.api.Test;
29+
import org.mockito.ArgumentMatchers;
2830

2931
import org.springframework.jdbc.core.JdbcOperations;
3032
import org.springframework.jdbc.core.JdbcTemplate;
33+
import org.springframework.jdbc.core.PreparedStatementSetter;
34+
import org.springframework.jdbc.core.RowMapper;
3135
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
3236
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
3337
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
3438

3539
import static org.assertj.core.api.Assertions.assertThat;
3640
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
41+
import static org.mockito.ArgumentMatchers.any;
3742
import static org.mockito.BDDMockito.given;
3843
import static org.mockito.Mockito.mock;
3944

@@ -145,6 +150,27 @@ void consumeWhenTokenDoesNotExistsThenReturnNull() {
145150
assertThat(consumedOneTimeToken).isNull();
146151
}
147152

153+
@Test
154+
void consumeWhenTokenIsDeletedConcurrentlyThenReturnNull() throws Exception {
155+
// Simulates a concurrent consume: SELECT finds the token but DELETE affects
156+
// 0 rows because another caller already consumed it.
157+
JdbcOperations jdbcOperations = mock(JdbcOperations.class);
158+
Instant notExpired = Instant.now().plus(5, ChronoUnit.MINUTES);
159+
OneTimeToken token = new DefaultOneTimeToken(TOKEN_VALUE, USERNAME, notExpired);
160+
given(jdbcOperations.query(any(String.class), any(PreparedStatementSetter.class),
161+
ArgumentMatchers.<RowMapper<OneTimeToken>>any()))
162+
.willReturn(List.of(token));
163+
given(jdbcOperations.update(any(String.class), any(PreparedStatementSetter.class))).willReturn(0);
164+
JdbcOneTimeTokenService service = new JdbcOneTimeTokenService(jdbcOperations);
165+
try {
166+
OneTimeToken consumed = service.consume(new OneTimeTokenAuthenticationToken(TOKEN_VALUE));
167+
assertThat(consumed).isNull();
168+
}
169+
finally {
170+
service.destroy();
171+
}
172+
}
173+
148174
@Test
149175
void consumeWhenTokenIsExpiredThenReturnNull() {
150176
GenerateOneTimeTokenRequest request = new GenerateOneTimeTokenRequest(USERNAME);

0 commit comments

Comments
 (0)