@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
/ *
* Copyright 2002 - 2024 the original author or authors .
* Copyright 2002 - 2025 the original author or authors .
*
* Licensed under the Apache License , Version 2 . 0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
@ -16,6 +16,7 @@
@@ -16,6 +16,7 @@
package org.springframework.security.config.annotation.web.configurers ;
import java.io.IOException ;
import java.util.List ;
import org.junit.jupiter.api.Test ;
@ -24,21 +25,37 @@ import org.junit.jupiter.api.extension.ExtendWith;
@@ -24,21 +25,37 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.context.annotation.Bean ;
import org.springframework.context.annotation.Configuration ;
import org.springframework.http.HttpInputMessage ;
import org.springframework.http.HttpOutputMessage ;
import org.springframework.http.converter.AbstractHttpMessageConverter ;
import org.springframework.http.converter.HttpMessageConverter ;
import org.springframework.http.converter.HttpMessageNotReadableException ;
import org.springframework.http.converter.HttpMessageNotWritableException ;
import org.springframework.security.authentication.TestingAuthenticationToken ;
import org.springframework.security.config.Customizer ;
import org.springframework.security.config.annotation.web.builders.HttpSecurity ;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity ;
import org.springframework.security.config.test.SpringTestContext ;
import org.springframework.security.config.test.SpringTestContextExtension ;
import org.springframework.security.core.context.SecurityContextHolder ;
import org.springframework.security.core.context.SecurityContextImpl ;
import org.springframework.security.core.userdetails.UserDetailsService ;
import org.springframework.security.provisioning.InMemoryUserDetailsManager ;
import org.springframework.security.web.FilterChainProxy ;
import org.springframework.security.web.SecurityFilterChain ;
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter ;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions ;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions ;
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations ;
import org.springframework.test.web.servlet.MockMvc ;
import static org.assertj.core.api.Assertions.assertThat ;
import static org.hamcrest.Matchers.containsString ;
import static org.mockito.ArgumentMatchers.any ;
import static org.mockito.BDDMockito.given ;
import static org.mockito.Mockito.mock ;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get ;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post ;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content ;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header ;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status ;
@ -126,6 +143,66 @@ public class WebAuthnConfigurerTests {
@@ -126,6 +143,66 @@ public class WebAuthnConfigurerTests {
this . mvc . perform ( get ( "/login/webauthn.js" ) ) . andExpect ( status ( ) . isNotFound ( ) ) ;
}
@Test
public void webauthnWhenConfiguredMessageConverter ( ) throws Exception {
TestingAuthenticationToken user = new TestingAuthenticationToken ( "user" , "password" , "ROLE_USER" ) ;
SecurityContextHolder . setContext ( new SecurityContextImpl ( user ) ) ;
PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions
. createPublicKeyCredentialCreationOptions ( )
. build ( ) ;
WebAuthnRelyingPartyOperations rpOperations = mock ( WebAuthnRelyingPartyOperations . class ) ;
ConfigMessageConverter . rpOperations = rpOperations ;
given ( rpOperations . createPublicKeyCredentialCreationOptions ( any ( ) ) ) . willReturn ( options ) ;
HttpMessageConverter < Object > converter = new AbstractHttpMessageConverter < > ( ) {
@Override
protected boolean supports ( Class < ? > clazz ) {
return true ;
}
@Override
protected Object readInternal ( Class < ? > clazz , HttpInputMessage inputMessage )
throws IOException , HttpMessageNotReadableException {
return null ;
}
@Override
protected void writeInternal ( Object o , HttpOutputMessage outputMessage )
throws IOException , HttpMessageNotWritableException {
outputMessage . getBody ( ) . write ( "123" . getBytes ( ) ) ;
}
} ;
ConfigMessageConverter . converter = converter ;
this . spring . register ( ConfigMessageConverter . class ) . autowire ( ) ;
this . mvc . perform ( post ( "/webauthn/register/options" ) )
. andExpect ( status ( ) . isOk ( ) )
. andExpect ( content ( ) . string ( "123" ) ) ;
}
@Configuration
@EnableWebSecurity
static class ConfigMessageConverter {
private static HttpMessageConverter < Object > converter ;
private static WebAuthnRelyingPartyOperations rpOperations ;
@Bean
WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations ( ) {
return ConfigMessageConverter . rpOperations ;
}
@Bean
UserDetailsService userDetailsService ( ) {
return new InMemoryUserDetailsManager ( ) ;
}
@Bean
SecurityFilterChain securityFilterChain ( HttpSecurity http ) throws Exception {
return http . csrf ( AbstractHttpConfigurer : : disable ) . webAuthn ( ( c ) - > c . messageConverter ( converter ) ) . build ( ) ;
}
}
@Configuration
@EnableWebSecurity
static class DefaultWebauthnConfiguration {