Skip to content

Commit 1f3dd53

Browse files
committed
Fix WebAuthn saves Anonymous PublicKeyCredentialUserEntity
Closes spring-projectsgh-16606
2 parents 26aa253 + a6b5c05 commit 1f3dd53

File tree

2 files changed

+61
-5
lines changed

2 files changed

+61
-5
lines changed

Diff for: web/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java

+13-4
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.
@@ -333,9 +333,7 @@ private static Set<AuthenticatorTransport> convertTransports(
333333
public PublicKeyCredentialRequestOptions createCredentialRequestOptions(
334334
PublicKeyCredentialRequestOptionsRequest request) {
335335
Authentication authentication = request.getAuthentication();
336-
// FIXME: do not load credentialRecords if anonymous
337-
PublicKeyCredentialUserEntity userEntity = findUserEntityOrCreateAndSave(authentication.getName());
338-
List<CredentialRecord> credentialRecords = this.userCredentials.findByUserId(userEntity.getId());
336+
List<CredentialRecord> credentialRecords = findCredentialRecords(authentication);
339337
return PublicKeyCredentialRequestOptions.builder()
340338
.allowCredentials(credentialDescriptors(credentialRecords))
341339
.challenge(Bytes.random())
@@ -346,6 +344,17 @@ public PublicKeyCredentialRequestOptions createCredentialRequestOptions(
346344
.build();
347345
}
348346

347+
private List<CredentialRecord> findCredentialRecords(Authentication authentication) {
348+
if (!this.trustResolver.isAuthenticated(authentication)) {
349+
return Collections.emptyList();
350+
}
351+
PublicKeyCredentialUserEntity userEntity = this.userEntities.findByUsername(authentication.getName());
352+
if (userEntity == null) {
353+
return Collections.emptyList();
354+
}
355+
return this.userCredentials.findByUserId(userEntity.getId());
356+
}
357+
349358
@Override
350359
public PublicKeyCredentialUserEntity authenticate(RelyingPartyAuthenticationRequest request) {
351360
PublicKeyCredentialRequestOptions requestOptions = request.getRequestOptions();

Diff for: web/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java

+48-1
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.
@@ -42,6 +42,8 @@
4242
import org.springframework.security.authentication.AnonymousAuthenticationToken;
4343
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
4444
import org.springframework.security.core.authority.AuthorityUtils;
45+
import org.springframework.security.core.userdetails.PasswordEncodedUser;
46+
import org.springframework.security.core.userdetails.UserDetails;
4547
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
4648
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse.AuthenticatorAttestationResponseBuilder;
4749
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
@@ -66,6 +68,7 @@
6668
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
6769
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
6870
import static org.mockito.BDDMockito.given;
71+
import static org.mockito.Mockito.verifyNoInteractions;
6972

7073
@ExtendWith(MockitoExtension.class)
7174
class Webauthn4jRelyingPartyOperationsTests {
@@ -536,6 +539,50 @@ void createCredentialRequestOptionsThenUserVerificationSameAsCreation() {
536539
.isEqualTo(creationOptions.getAuthenticatorSelection().getUserVerification());
537540
}
538541

542+
@Test
543+
void createCredentialRequestOptionsWhenAnonymousAuthentication() {
544+
AnonymousAuthenticationToken authentication = new AnonymousAuthenticationToken("key", "anonymousUser",
545+
Set.of(() -> "ROLE_ANONYMOUS"));
546+
PublicKeyCredentialRequestOptionsRequest createRequest = new ImmutablePublicKeyCredentialRequestOptionsRequest(
547+
authentication);
548+
PublicKeyCredentialRequestOptions credentialRequestOptions = this.rpOperations
549+
.createCredentialRequestOptions(createRequest);
550+
551+
assertThat(credentialRequestOptions.getAllowCredentials()).isEmpty();
552+
// verify anonymous user not saved
553+
verifyNoInteractions(this.userEntities);
554+
}
555+
556+
@Test
557+
void createCredentialRequestOptionsWhenNullAuthentication() {
558+
PublicKeyCredentialRequestOptionsRequest createRequest = new ImmutablePublicKeyCredentialRequestOptionsRequest(
559+
null);
560+
PublicKeyCredentialRequestOptions credentialRequestOptions = this.rpOperations
561+
.createCredentialRequestOptions(createRequest);
562+
563+
assertThat(credentialRequestOptions.getAllowCredentials()).isEmpty();
564+
// verify anonymous user not saved
565+
verifyNoInteractions(this.userEntities);
566+
}
567+
568+
@Test
569+
void createCredentialRequestOptionsWhenAuthenticated() {
570+
UserDetails user = PasswordEncodedUser.user();
571+
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null,
572+
user.getAuthorities());
573+
PublicKeyCredentialUserEntity userEntity = TestPublicKeyCredentialUserEntity.userEntity().build();
574+
CredentialRecord credentialRecord = TestCredentialRecord.userCredential().build();
575+
given(this.userEntities.findByUsername(user.getUsername())).willReturn(userEntity);
576+
given(this.userCredentials.findByUserId(userEntity.getId())).willReturn(Arrays.asList(credentialRecord));
577+
PublicKeyCredentialRequestOptionsRequest createRequest = new ImmutablePublicKeyCredentialRequestOptionsRequest(
578+
auth);
579+
PublicKeyCredentialRequestOptions credentialRequestOptions = this.rpOperations
580+
.createCredentialRequestOptions(createRequest);
581+
582+
assertThat(credentialRequestOptions.getAllowCredentials()).extracting(PublicKeyCredentialDescriptor::getId)
583+
.containsExactly(credentialRecord.getCredentialId());
584+
}
585+
539586
private static AuthenticatorAttestationResponse setFlag(byte... flags) throws Exception {
540587
AuthenticatorAttestationResponseBuilder authAttResponseBldr = TestAuthenticatorAttestationResponse
541588
.createAuthenticatorAttestationResponse();

0 commit comments

Comments
 (0)