|
|
|
@ -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"); |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
@ -16,6 +16,7 @@ |
|
|
|
|
|
|
|
|
|
|
|
package org.springframework.security.config.annotation.web.configurers; |
|
|
|
package org.springframework.security.config.annotation.web.configurers; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import java.io.IOException; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
|
|
|
|
|
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
@ -24,21 +25,37 @@ import org.junit.jupiter.api.extension.ExtendWith; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.context.annotation.Bean; |
|
|
|
import org.springframework.context.annotation.Bean; |
|
|
|
import org.springframework.context.annotation.Configuration; |
|
|
|
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.Customizer; |
|
|
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|
|
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|
|
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
|
|
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
|
|
|
import org.springframework.security.config.test.SpringTestContext; |
|
|
|
import org.springframework.security.config.test.SpringTestContext; |
|
|
|
import org.springframework.security.config.test.SpringTestContextExtension; |
|
|
|
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.core.userdetails.UserDetailsService; |
|
|
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager; |
|
|
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager; |
|
|
|
import org.springframework.security.web.FilterChainProxy; |
|
|
|
import org.springframework.security.web.FilterChainProxy; |
|
|
|
import org.springframework.security.web.SecurityFilterChain; |
|
|
|
import org.springframework.security.web.SecurityFilterChain; |
|
|
|
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; |
|
|
|
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 org.springframework.test.web.servlet.MockMvc; |
|
|
|
|
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
import static org.hamcrest.Matchers.containsString; |
|
|
|
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.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.content; |
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; |
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; |
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|
|
|
@ -126,6 +143,66 @@ public class WebAuthnConfigurerTests { |
|
|
|
this.mvc.perform(get("/login/webauthn.js")).andExpect(status().isNotFound()); |
|
|
|
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 |
|
|
|
@Configuration |
|
|
|
@EnableWebSecurity |
|
|
|
@EnableWebSecurity |
|
|
|
static class DefaultWebauthnConfiguration { |
|
|
|
static class DefaultWebauthnConfiguration { |
|
|
|
|