Skip to content

Commit 85f0f3f

Browse files
topiamjzheaux
authored andcommitted
Support Custom RequestMatchers for WebAuthn
Closes gh-16517 Signed-off-by: topiam <[email protected]>
1 parent fa35c5b commit 85f0f3f

File tree

6 files changed

+137
-0
lines changed

6 files changed

+137
-0
lines changed

web/src/main/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsFilter.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ public PublicKeyCredentialRequestOptionsFilter(WebAuthnRelyingPartyOperations rp
7575
this.rpOptions = rpOptions;
7676
}
7777

78+
/**
79+
* Sets the {@link RequestMatcher} used to trigger this filter. By default, the
80+
* {@link RequestMatcher} is {@code POST /webauthn/authenticate/options}.
81+
* @param requestMatcher the {@link RequestMatcher} to use
82+
* @since 6.5
83+
*/
84+
public void setRequestMatcher(RequestMatcher requestMatcher) {
85+
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
86+
this.matcher = requestMatcher;
87+
}
88+
7889
@Override
7990
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
8091
throws ServletException, IOException {

web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ public PublicKeyCredentialCreationOptionsFilter(WebAuthnRelyingPartyOperations r
8282
this.rpOperations = rpOperations;
8383
}
8484

85+
/**
86+
* Sets the {@link RequestMatcher} used to trigger this filter.
87+
* <p>
88+
* By default, the {@link RequestMatcher} is {@code POST /webauthn/register/options}.
89+
* @param requestMatcher the {@link RequestMatcher} to use
90+
* @since 6.5
91+
*/
92+
public void setRequestMatcher(RequestMatcher requestMatcher) {
93+
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
94+
this.matcher = requestMatcher;
95+
}
96+
8597
@Override
8698
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
8799
throws ServletException, IOException {

web/src/main/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilter.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,32 @@ public WebAuthnRegistrationFilter(UserCredentialRepository userCredentials,
105105
this.rpOptions = rpOptions;
106106
}
107107

108+
/**
109+
* Sets the {@link RequestMatcher} to trigger this filter's the credential
110+
* registration operation .
111+
* <p/>
112+
* By default, the {@link RequestMatcher} is {@code POST /webauthn/register}.
113+
* @param registerCredentialMatcher the {@link RequestMatcher} to use
114+
* @since 6.5
115+
*/
116+
public void setRegisterCredentialMatcher(RequestMatcher registerCredentialMatcher) {
117+
Assert.notNull(registerCredentialMatcher, "registerCredentialMatcher cannot be null");
118+
this.registerCredentialMatcher = registerCredentialMatcher;
119+
}
120+
121+
/**
122+
* Sets the {@link RequestMatcher} to trigger this filter's the credential removal
123+
* operation .
124+
* <p/>
125+
* By default, the {@link RequestMatcher} is {@code DELETE /webauthn/register/{id}}.
126+
* @param removeCredentialMatcher the {@link RequestMatcher} to use
127+
* @since 6.5
128+
*/
129+
public void setRemoveCredentialMatcher(RequestMatcher removeCredentialMatcher) {
130+
Assert.notNull(removeCredentialMatcher, "removeCredentialMatcher cannot be null");
131+
this.removeCredentialMatcher = removeCredentialMatcher;
132+
}
133+
108134
@Override
109135
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
110136
throws ServletException, IOException {

web/src/test/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsFilterTests.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.nio.charset.StandardCharsets;
2020

21+
import jakarta.servlet.FilterChain;
2122
import org.junit.jupiter.api.AfterEach;
2223
import org.junit.jupiter.api.BeforeEach;
2324
import org.junit.jupiter.api.Test;
@@ -30,10 +31,13 @@
3031

3132
import org.springframework.http.converter.HttpMessageConverter;
3233
import org.springframework.http.server.ServletServerHttpResponse;
34+
import org.springframework.mock.web.MockHttpServletRequest;
35+
import org.springframework.mock.web.MockHttpServletResponse;
3336
import org.springframework.security.authentication.TestingAuthenticationToken;
3437
import org.springframework.security.core.context.SecurityContextHolder;
3538
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3639
import org.springframework.security.core.context.SecurityContextImpl;
40+
import org.springframework.security.web.util.matcher.RequestMatcher;
3741
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
3842
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
3943
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions;
@@ -48,6 +52,8 @@
4852
import static org.mockito.BDDMockito.given;
4953
import static org.mockito.BDDMockito.verifyNoInteractions;
5054
import static org.mockito.BDDMockito.willAnswer;
55+
import static org.mockito.Mockito.mock;
56+
import static org.mockito.Mockito.verify;
5157
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
5258
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
5359
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -75,20 +81,35 @@ class PublicKeyCredentialRequestOptionsFilterTests {
7581

7682
private PublicKeyCredentialRequestOptionsFilter filter;
7783

84+
private MockHttpServletRequest request;
85+
86+
private MockHttpServletResponse response;
87+
7888
private MockMvc mockMvc;
7989

8090
@BeforeEach
8191
void setup() {
8292
this.filter = new PublicKeyCredentialRequestOptionsFilter(this.relyingPartyOperations);
8393
this.filter.setRequestOptionsRepository(this.requestOptionsRepository);
8494
this.mockMvc = MockMvcBuilders.standaloneSetup().addFilter(this.filter).build();
95+
this.request = new MockHttpServletRequest();
96+
this.response = new MockHttpServletResponse();
8597
}
8698

8799
@AfterEach
88100
void cleanup() {
89101
SecurityContextHolder.clearContext();
90102
}
91103

104+
@Test
105+
void doFilterWhenCustomRequestMatcherThenUses() throws Exception {
106+
RequestMatcher requestMatcher = mock(RequestMatcher.class);
107+
this.filter.setRequestMatcher(requestMatcher);
108+
FilterChain mock = mock(FilterChain.class);
109+
this.filter.doFilter(this.request, this.response, mock);
110+
verify(requestMatcher).matches(any());
111+
}
112+
92113
@Test
93114
void constructorWhenNull() {
94115
assertThatExceptionOfType(IllegalArgumentException.class)

web/src/test/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilterTests.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import java.util.Arrays;
2020

21+
import jakarta.servlet.FilterChain;
2122
import org.junit.jupiter.api.AfterEach;
23+
import org.junit.jupiter.api.BeforeEach;
2224
import org.junit.jupiter.api.Test;
2325
import org.junit.jupiter.api.extension.ExtendWith;
2426
import org.mockito.Mock;
@@ -27,12 +29,14 @@
2729
import org.springframework.http.HttpHeaders;
2830
import org.springframework.http.HttpStatus;
2931
import org.springframework.http.MediaType;
32+
import org.springframework.mock.web.MockHttpServletRequest;
3033
import org.springframework.mock.web.MockHttpServletResponse;
3134
import org.springframework.security.authentication.AnonymousAuthenticationToken;
3235
import org.springframework.security.authentication.TestingAuthenticationToken;
3336
import org.springframework.security.core.authority.AuthorityUtils;
3437
import org.springframework.security.core.context.SecurityContextHolder;
3538
import org.springframework.security.core.context.SecurityContextImpl;
39+
import org.springframework.security.web.util.matcher.RequestMatcher;
3640
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
3741
import org.springframework.security.web.webauthn.api.Bytes;
3842
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
@@ -47,6 +51,8 @@
4751
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
4852
import static org.mockito.ArgumentMatchers.any;
4953
import static org.mockito.BDDMockito.given;
54+
import static org.mockito.Mockito.mock;
55+
import static org.mockito.Mockito.verify;
5056
import static org.mockito.Mockito.verifyNoInteractions;
5157
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
5258
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -68,11 +74,38 @@ class PublicKeyCredentialCreationOptionsFilterTests {
6874
@Mock
6975
private WebAuthnRelyingPartyOperations rpOperations;
7076

77+
private PublicKeyCredentialCreationOptionsFilter filter;
78+
79+
private MockHttpServletRequest request;
80+
81+
private MockHttpServletResponse response;
82+
83+
@BeforeEach
84+
void setup() {
85+
this.filter = new PublicKeyCredentialCreationOptionsFilter(this.rpOperations);
86+
this.request = new MockHttpServletRequest();
87+
this.response = new MockHttpServletResponse();
88+
}
89+
7190
@AfterEach
7291
void clear() {
7392
SecurityContextHolder.clearContext();
7493
}
7594

95+
@Test
96+
void doFilterWhenCustomRequestMatcherThenUses() throws Exception {
97+
RequestMatcher requestMatcher = mock(RequestMatcher.class);
98+
this.filter.setRequestMatcher(requestMatcher);
99+
FilterChain mock = mock(FilterChain.class);
100+
this.filter.doFilter(this.request, this.response, mock);
101+
verify(requestMatcher).matches(any());
102+
}
103+
104+
@Test
105+
void setRequestMatcherWhenNullThenIllegalArgument() {
106+
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequestMatcher(null));
107+
}
108+
76109
@Test
77110
void constructorWhenRpOperationsIsNullThenIllegalArgumentException() {
78111
assertThatIllegalArgumentException().isThrownBy(() -> new PublicKeyCredentialCreationOptionsFilter(null))

web/src/test/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilterTests.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.mock.web.MockHttpServletRequest;
3131
import org.springframework.mock.web.MockHttpServletResponse;
3232
import org.springframework.mock.web.MockServletContext;
33+
import org.springframework.security.web.util.matcher.RequestMatcher;
3334
import org.springframework.security.web.webauthn.api.ImmutableCredentialRecord;
3435
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
3536
import org.springframework.security.web.webauthn.api.TestCredentialRecord;
@@ -100,9 +101,42 @@ class WebAuthnRegistrationFilterTests {
100101

101102
private WebAuthnRegistrationFilter filter;
102103

104+
private MockHttpServletRequest request;
105+
103106
@BeforeEach
104107
void setup() {
105108
this.filter = new WebAuthnRegistrationFilter(this.userCredentials, this.operations);
109+
this.request = new MockHttpServletRequest();
110+
this.response = new MockHttpServletResponse();
111+
this.chain = mock(FilterChain.class);
112+
}
113+
114+
@Test
115+
void doFilterWhenCustomRequestRegisterCredentialMatcherThenUses() throws Exception {
116+
RequestMatcher requestMatcher = mock(RequestMatcher.class);
117+
this.filter.setRegisterCredentialMatcher(requestMatcher);
118+
FilterChain mock = mock(FilterChain.class);
119+
this.filter.doFilter(this.request, this.response, mock);
120+
verify(requestMatcher).matches(any());
121+
}
122+
123+
@Test
124+
void doFilterWhenCustomRequestRemoveCredentialMatcherThenUses() throws Exception {
125+
RequestMatcher requestMatcher = mock(RequestMatcher.class);
126+
this.filter.setRemoveCredentialMatcher(requestMatcher);
127+
FilterChain mock = mock(FilterChain.class);
128+
this.filter.doFilter(this.request, this.response, mock);
129+
verify(requestMatcher).matches(any());
130+
}
131+
132+
@Test
133+
void setRequestRegisterCredentialWhenNullThenIllegalArgument() {
134+
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRegisterCredentialMatcher(null));
135+
}
136+
137+
@Test
138+
void setRequestRemoveCredentialWhenNullThenIllegalArgument() {
139+
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRemoveCredentialMatcher(null));
106140
}
107141

108142
@Test

0 commit comments

Comments
 (0)