Skip to content

Commit cbd77e0

Browse files
committed
Set HttpMessageConverter by DSL
Closes spring-projectsgh-16369 Signed-off-by: DingHao <[email protected]>
1 parent bf9b95a commit cbd77e0

File tree

3 files changed

+116
-6
lines changed

3 files changed

+116
-6
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java

+24-3
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.
@@ -23,6 +23,7 @@
2323

2424
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2525
import org.springframework.context.ApplicationContext;
26+
import org.springframework.http.converter.HttpMessageConverter;
2627
import org.springframework.security.authentication.ProviderManager;
2728
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2829
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -63,6 +64,8 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
6364

6465
private boolean disableDefaultRegistrationPage = false;
6566

67+
private HttpMessageConverter<Object> converter;
68+
6669
/**
6770
* The Relying Party id.
6871
* @param rpId the relying party id
@@ -116,6 +119,16 @@ public WebAuthnConfigurer<H> disableDefaultRegistrationPage(boolean disable) {
116119
return this;
117120
}
118121

122+
/**
123+
* Sets PublicKeyCredentialCreationOptionsRepository
124+
* @param converter the creationOptionsRepository
125+
* @return the {@link WebAuthnConfigurer} for further customization
126+
*/
127+
public WebAuthnConfigurer<H> messageConverter(HttpMessageConverter<Object> converter) {
128+
this.converter = converter;
129+
return this;
130+
}
131+
119132
@Override
120133
public void configure(H http) throws Exception {
121134
UserDetailsService userDetailsService = getSharedOrBean(http, UserDetailsService.class).orElseGet(() -> {
@@ -130,9 +143,17 @@ public void configure(H http) throws Exception {
130143
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
131144
webAuthnAuthnFilter.setAuthenticationManager(
132145
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
146+
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
147+
rpOperations);
148+
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
149+
rpOperations);
150+
if (this.converter != null) {
151+
webAuthnRegistrationFilter.setConverter(this.converter);
152+
creationOptionsFilter.setConverter(this.converter);
153+
}
133154
http.addFilterBefore(webAuthnAuthnFilter, BasicAuthenticationFilter.class);
134-
http.addFilterAfter(new WebAuthnRegistrationFilter(userCredentials, rpOperations), AuthorizationFilter.class);
135-
http.addFilterBefore(new PublicKeyCredentialCreationOptionsFilter(rpOperations), AuthorizationFilter.class);
155+
http.addFilterAfter(webAuthnRegistrationFilter, AuthorizationFilter.class);
156+
http.addFilterBefore(creationOptionsFilter, AuthorizationFilter.class);
136157
http.addFilterBefore(new PublicKeyCredentialRequestOptionsFilter(rpOperations), AuthorizationFilter.class);
137158

138159
DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http

config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java

+78-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.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.config.annotation.web.configurers;
1818

19+
import java.io.IOException;
1920
import java.util.List;
2021

2122
import org.junit.jupiter.api.Test;
@@ -24,21 +25,37 @@
2425
import org.springframework.beans.factory.annotation.Autowired;
2526
import org.springframework.context.annotation.Bean;
2627
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.http.HttpInputMessage;
29+
import org.springframework.http.HttpOutputMessage;
30+
import org.springframework.http.converter.AbstractHttpMessageConverter;
31+
import org.springframework.http.converter.HttpMessageConverter;
32+
import org.springframework.http.converter.HttpMessageNotReadableException;
33+
import org.springframework.http.converter.HttpMessageNotWritableException;
34+
import org.springframework.security.authentication.TestingAuthenticationToken;
2735
import org.springframework.security.config.Customizer;
2836
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2937
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
3038
import org.springframework.security.config.test.SpringTestContext;
3139
import org.springframework.security.config.test.SpringTestContextExtension;
40+
import org.springframework.security.core.context.SecurityContextHolder;
41+
import org.springframework.security.core.context.SecurityContextImpl;
3242
import org.springframework.security.core.userdetails.UserDetailsService;
3343
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
3444
import org.springframework.security.web.FilterChainProxy;
3545
import org.springframework.security.web.SecurityFilterChain;
3646
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
47+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
48+
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
49+
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
3750
import org.springframework.test.web.servlet.MockMvc;
3851

3952
import static org.assertj.core.api.Assertions.assertThat;
4053
import static org.hamcrest.Matchers.containsString;
54+
import static org.mockito.ArgumentMatchers.any;
55+
import static org.mockito.BDDMockito.given;
56+
import static org.mockito.Mockito.mock;
4157
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
58+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
4259
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
4360
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
4461
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -126,6 +143,66 @@ public void webauthnWhenConfiguredAndNoDefaultRegistrationPageThenDoesNotServeJa
126143
this.mvc.perform(get("/login/webauthn.js")).andExpect(status().isNotFound());
127144
}
128145

146+
@Test
147+
public void webauthnWhenConfiguredMessageConverter() throws Exception {
148+
TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
149+
SecurityContextHolder.setContext(new SecurityContextImpl(user));
150+
PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions
151+
.createPublicKeyCredentialCreationOptions()
152+
.build();
153+
WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class);
154+
ConfigMessageConverter.rpOperations = rpOperations;
155+
given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options);
156+
HttpMessageConverter<Object> converter = new AbstractHttpMessageConverter<>() {
157+
@Override
158+
protected boolean supports(Class<?> clazz) {
159+
return true;
160+
}
161+
162+
@Override
163+
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
164+
throws IOException, HttpMessageNotReadableException {
165+
return null;
166+
}
167+
168+
@Override
169+
protected void writeInternal(Object o, HttpOutputMessage outputMessage)
170+
throws IOException, HttpMessageNotWritableException {
171+
outputMessage.getBody().write("123".getBytes());
172+
}
173+
};
174+
ConfigMessageConverter.converter = converter;
175+
this.spring.register(ConfigMessageConverter.class).autowire();
176+
this.mvc.perform(post("/webauthn/register/options"))
177+
.andExpect(status().isOk())
178+
.andExpect(content().string("123"));
179+
}
180+
181+
@Configuration
182+
@EnableWebSecurity
183+
static class ConfigMessageConverter {
184+
185+
private static HttpMessageConverter<Object> converter;
186+
187+
private static WebAuthnRelyingPartyOperations rpOperations;
188+
189+
@Bean
190+
WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() {
191+
return ConfigMessageConverter.rpOperations;
192+
}
193+
194+
@Bean
195+
UserDetailsService userDetailsService() {
196+
return new InMemoryUserDetailsManager();
197+
}
198+
199+
@Bean
200+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
201+
return http.csrf(AbstractHttpConfigurer::disable).webAuthn((c) -> c.messageConverter(converter)).build();
202+
}
203+
204+
}
205+
129206
@Configuration
130207
@EnableWebSecurity
131208
static class DefaultWebauthnConfiguration {

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

+14-2
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.
@@ -53,6 +53,7 @@
5353
* {@link PublicKeyCredentialCreationOptions} for <a href=
5454
* "https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-create">creating</a>
5555
* a new credential.
56+
* @author DingHao
5657
*/
5758
public class PublicKeyCredentialCreationOptionsFilter extends OncePerRequestFilter {
5859

@@ -67,7 +68,7 @@ public class PublicKeyCredentialCreationOptionsFilter extends OncePerRequestFilt
6768

6869
private final WebAuthnRelyingPartyOperations rpOperations;
6970

70-
private final HttpMessageConverter<Object> converter = new MappingJackson2HttpMessageConverter(
71+
private HttpMessageConverter<Object> converter = new MappingJackson2HttpMessageConverter(
7172
Jackson2ObjectMapperBuilder.json().modules(new WebauthnJackson2Module()).build());
7273

7374
/**
@@ -103,4 +104,15 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
103104
this.converter.write(options, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));
104105
}
105106

107+
/**
108+
* Set the {@link HttpMessageConverter} to read the
109+
* {@link WebAuthnRegistrationFilter.WebAuthnRegistrationRequest} and write the response. The default is
110+
* {@link MappingJackson2HttpMessageConverter}.
111+
* @param converter the {@link HttpMessageConverter} to use. Cannot be null.
112+
*/
113+
public void setConverter(HttpMessageConverter<Object> converter) {
114+
Assert.notNull(converter, "converter cannot be null");
115+
this.converter = converter;
116+
}
117+
106118
}

0 commit comments

Comments
 (0)