|
@@ -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.nio.charset.StandardCharsets;
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.Test;
|
|
@@ -24,21 +25,34 @@ 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.HttpOutputMessage;
|
|
|
|
+import org.springframework.http.converter.HttpMessageConverter;
|
|
|
|
+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.BDDMockito.willAnswer;
|
|
|
|
+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 +140,56 @@ 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 = mock(HttpMessageConverter.class);
|
|
|
|
+ given(converter.canWrite(any(), any())).willReturn(true);
|
|
|
|
+ String expectedBody = "123";
|
|
|
|
+ willAnswer((args) -> {
|
|
|
|
+ HttpOutputMessage out = (HttpOutputMessage) args.getArguments()[2];
|
|
|
|
+ out.getBody().write(expectedBody.getBytes(StandardCharsets.UTF_8));
|
|
|
|
+ return null;
|
|
|
|
+ }).given(converter).write(any(), any(), any());
|
|
|
|
+ ConfigMessageConverter.converter = converter;
|
|
|
|
+ this.spring.register(ConfigMessageConverter.class).autowire();
|
|
|
|
+ this.mvc.perform(post("/webauthn/register/options"))
|
|
|
|
+ .andExpect(status().isOk())
|
|
|
|
+ .andExpect(content().string(expectedBody));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @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 {
|