Browse Source

Set PublicKeyCredentialCreationOptionsRepository by DSL or Bean

Closes gh-16369

Signed-off-by: DingHao <dh.hiekn@gmail.com>
DingHao 7 tháng trước cách đây
mục cha
commit
f4491f388e

+ 27 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java

@@ -44,6 +44,7 @@ import org.springframework.security.web.webauthn.management.WebAuthnRelyingParty
 import org.springframework.security.web.webauthn.management.Webauthn4JRelyingPartyOperations;
 import org.springframework.security.web.webauthn.registration.DefaultWebAuthnRegistrationPageGeneratingFilter;
 import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsFilter;
+import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsRepository;
 import org.springframework.security.web.webauthn.registration.WebAuthnRegistrationFilter;
 
 /**
@@ -64,6 +65,8 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
 
 	private boolean disableDefaultRegistrationPage = false;
 
+	private PublicKeyCredentialCreationOptionsRepository creationOptionsRepository;
+
 	private HttpMessageConverter<Object> converter;
 
 	/**
@@ -130,6 +133,17 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
 		return this;
 	}
 
+	/**
+	 * Sets PublicKeyCredentialCreationOptionsRepository
+	 * @param creationOptionsRepository the creationOptionsRepository
+	 * @return the {@link WebAuthnConfigurer} for further customization
+	 */
+	public WebAuthnConfigurer<H> creationOptionsRepository(
+			PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) {
+		this.creationOptionsRepository = creationOptionsRepository;
+		return this;
+	}
+
 	@Override
 	public void configure(H http) throws Exception {
 		UserDetailsService userDetailsService = getSharedOrBean(http, UserDetailsService.class).orElseGet(() -> {
@@ -141,6 +155,7 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
 		UserCredentialRepository userCredentials = getSharedOrBean(http, UserCredentialRepository.class)
 			.orElse(userCredentialRepository());
 		WebAuthnRelyingPartyOperations rpOperations = webAuthnRelyingPartyOperations(userEntities, userCredentials);
+		PublicKeyCredentialCreationOptionsRepository creationOptionsRepository = creationOptionsRepository();
 		WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
 		webAuthnAuthnFilter.setAuthenticationManager(
 				new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
@@ -148,6 +163,10 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
 				rpOperations);
 		PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
 				rpOperations);
+		if (creationOptionsRepository != null) {
+			webAuthnRegistrationFilter.setCreationOptionsRepository(creationOptionsRepository);
+			creationOptionsFilter.setCreationOptionsRepository(creationOptionsRepository);
+		}
 		if (this.converter != null) {
 			webAuthnRegistrationFilter.setConverter(this.converter);
 			creationOptionsFilter.setConverter(this.converter);
@@ -181,6 +200,14 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
 		}
 	}
 
+	private PublicKeyCredentialCreationOptionsRepository creationOptionsRepository() {
+		if (this.creationOptionsRepository != null) {
+			return this.creationOptionsRepository;
+		}
+		ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
+		return context.getBeanProvider(PublicKeyCredentialCreationOptionsRepository.class).getIfUnique();
+	}
+
 	private <C> Optional<C> getSharedOrBean(H http, Class<C> type) {
 		C shared = http.getSharedObject(type);
 		return Optional.ofNullable(shared).or(() -> getBeanOrNull(type));

+ 102 - 3
config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java

@@ -43,6 +43,7 @@ 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.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
 import org.springframework.test.web.servlet.MockMvc;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -55,6 +56,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 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.request;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
 /**
@@ -141,13 +143,53 @@ public class WebAuthnConfigurerTests {
 	}
 
 	@Test
-	public void webauthnWhenConfiguredMessageConverter() throws Exception {
+	public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepository() 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);
+		ConfigCredentialCreationOptionsRepository.rpOperations = rpOperations;
+		given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options);
+		String attrName = "attrName";
+		HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository();
+		creationOptionsRepository.setAttrName(attrName);
+		ConfigCredentialCreationOptionsRepository.creationOptionsRepository = creationOptionsRepository;
+		this.spring.register(ConfigCredentialCreationOptionsRepository.class).autowire();
+		this.mvc.perform(post("/webauthn/register/options"))
+			.andExpect(status().isOk())
+			.andExpect(request().sessionAttribute(attrName, options));
+	}
+
+	@Test
+	public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepositoryBeanPresent() 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);
+		ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations = rpOperations;
+		given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options);
+		String attrName = "attrName";
+		HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository();
+		creationOptionsRepository.setAttrName(attrName);
+		ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository = creationOptionsRepository;
+		this.spring.register(ConfigCredentialCreationOptionsRepositoryFromBean.class).autowire();
+		this.mvc.perform(post("/webauthn/register/options"))
+			.andExpect(status().isOk())
+			.andExpect(request().sessionAttribute(attrName, options));
+	}
+
+	@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);
@@ -161,8 +203,65 @@ public class WebAuthnConfigurerTests {
 		ConfigMessageConverter.converter = converter;
 		this.spring.register(ConfigMessageConverter.class).autowire();
 		this.mvc.perform(post("/webauthn/register/options"))
-			.andExpect(status().isOk())
-			.andExpect(content().string(expectedBody));
+				.andExpect(status().isOk())
+				.andExpect(content().string(expectedBody));
+	}
+
+	@Configuration
+	@EnableWebSecurity
+	static class ConfigCredentialCreationOptionsRepository {
+
+		private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository;
+
+		private static WebAuthnRelyingPartyOperations rpOperations;
+
+		@Bean
+		WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() {
+			return ConfigCredentialCreationOptionsRepository.rpOperations;
+		}
+
+		@Bean
+		UserDetailsService userDetailsService() {
+			return new InMemoryUserDetailsManager();
+		}
+
+		@Bean
+		SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+			return http.csrf(AbstractHttpConfigurer::disable)
+				.webAuthn((c) -> c.creationOptionsRepository(creationOptionsRepository))
+				.build();
+		}
+
+	}
+
+	@Configuration
+	@EnableWebSecurity
+	static class ConfigCredentialCreationOptionsRepositoryFromBean {
+
+		private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository;
+
+		private static WebAuthnRelyingPartyOperations rpOperations;
+
+		@Bean
+		WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() {
+			return ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations;
+		}
+
+		@Bean
+		UserDetailsService userDetailsService() {
+			return new InMemoryUserDetailsManager();
+		}
+
+		@Bean
+		HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository() {
+			return ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository;
+		}
+
+		@Bean
+		SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+			return http.csrf(AbstractHttpConfigurer::disable).webAuthn(Customizer.withDefaults()).build();
+		}
+
 	}
 
 	@Configuration

+ 11 - 0
web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java

@@ -105,6 +105,17 @@ public class PublicKeyCredentialCreationOptionsFilter extends OncePerRequestFilt
 		this.converter.write(options, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));
 	}
 
+	/**
+	 * Sets the {@link PublicKeyCredentialCreationOptionsRepository} to use. The default
+	 * is {@link HttpSessionPublicKeyCredentialCreationOptionsRepository}.
+	 * @param creationOptionsRepository the
+	 * {@link PublicKeyCredentialCreationOptionsRepository} to use. Cannot be null.
+	 */
+	public void setCreationOptionsRepository(PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) {
+		Assert.notNull(creationOptionsRepository, "creationOptionsRepository cannot be null");
+		this.repository = creationOptionsRepository;
+	}
+
 	/**
 	 * Set the {@link HttpMessageConverter} to read the
 	 * {@link WebAuthnRegistrationFilter.WebAuthnRegistrationRequest} and write the