webauthn: introduce WebAuthnConfigurer#disableDefaultRegistrationPage
This commit is contained in:
committed by
Rob Winch
parent
de7c452e42
commit
2639ac6545
+28
-3
@@ -61,6 +61,8 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
|
||||
private Set<String> allowedOrigins = new HashSet<>();
|
||||
|
||||
private boolean disableDefaultRegistrationPage = false;
|
||||
|
||||
/**
|
||||
* The Relying Party id.
|
||||
* @param rpId the relying party id
|
||||
@@ -102,6 +104,18 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures whether the default webauthn registration should be disabled. Setting it
|
||||
* to {@code true} will prevent the configurer from registering the
|
||||
* {@link DefaultWebAuthnRegistrationPageGeneratingFilter}.
|
||||
* @param disable disable the default registration page if true, enable it otherwise
|
||||
* @return the {@link WebAuthnConfigurer} for further customization
|
||||
*/
|
||||
public WebAuthnConfigurer<H> disableDefaultRegistrationPage(boolean disable) {
|
||||
this.disableDefaultRegistrationPage = disable;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(H http) throws Exception {
|
||||
UserDetailsService userDetailsService = getSharedOrBean(http, UserDetailsService.class).orElseGet(() -> {
|
||||
@@ -119,17 +133,28 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
http.addFilterBefore(webAuthnAuthnFilter, BasicAuthenticationFilter.class);
|
||||
http.addFilterAfter(new WebAuthnRegistrationFilter(userCredentials, rpOperations), AuthorizationFilter.class);
|
||||
http.addFilterBefore(new PublicKeyCredentialCreationOptionsFilter(rpOperations), AuthorizationFilter.class);
|
||||
http.addFilterAfter(new DefaultWebAuthnRegistrationPageGeneratingFilter(userEntities, userCredentials),
|
||||
AuthorizationFilter.class);
|
||||
http.addFilterBefore(new PublicKeyCredentialRequestOptionsFilter(rpOperations), AuthorizationFilter.class);
|
||||
|
||||
DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http
|
||||
.getSharedObject(DefaultLoginPageGeneratingFilter.class);
|
||||
if (loginPageGeneratingFilter != null) {
|
||||
boolean isLoginPageEnabled = loginPageGeneratingFilter != null && loginPageGeneratingFilter.isEnabled();
|
||||
if (isLoginPageEnabled) {
|
||||
loginPageGeneratingFilter.setPasskeysEnabled(true);
|
||||
loginPageGeneratingFilter.setResolveHeaders((request) -> {
|
||||
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
|
||||
return Map.of(csrfToken.getHeaderName(), csrfToken.getToken());
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.disableDefaultRegistrationPage) {
|
||||
http.addFilterAfter(new DefaultWebAuthnRegistrationPageGeneratingFilter(userEntities, userCredentials),
|
||||
AuthorizationFilter.class);
|
||||
if (!isLoginPageEnabled) {
|
||||
http.addFilter(DefaultResourcesFilter.css());
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoginPageEnabled || !this.disableDefaultRegistrationPage) {
|
||||
http.addFilter(DefaultResourcesFilter.webauthn());
|
||||
}
|
||||
}
|
||||
|
||||
+121
-1
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@@ -29,9 +31,12 @@ import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
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.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
@@ -50,7 +55,7 @@ public class WebAuthnConfigurerTests {
|
||||
MockMvc mvc;
|
||||
|
||||
@Test
|
||||
public void javascriptWhenWebauthnConfiguredThenServesJavascript() throws Exception {
|
||||
public void webauthnWhenConfiguredConfiguredThenServesJavascript() throws Exception {
|
||||
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
|
||||
this.mvc.perform(get("/login/webauthn.js"))
|
||||
.andExpect(status().isOk())
|
||||
@@ -58,6 +63,69 @@ public class WebAuthnConfigurerTests {
|
||||
.andExpect(content().string(containsString("async function authenticate(")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webauthnWhenConfiguredConfiguredThenServesCss() throws Exception {
|
||||
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
|
||||
this.mvc.perform(get("/default-ui.css"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string("content-type", "text/css;charset=UTF-8"))
|
||||
.andExpect(content().string(containsString("body {")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception {
|
||||
this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire();
|
||||
this.mvc.perform(get("/login/webauthn.js"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string("content-type", "text/javascript;charset=UTF-8"))
|
||||
.andExpect(content().string(containsString("async function authenticate(")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesCss() throws Exception {
|
||||
this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire();
|
||||
this.mvc.perform(get("/default-ui.css"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string("content-type", "text/css;charset=UTF-8"))
|
||||
.andExpect(content().string(containsString("body {")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webauthnWhenFormLoginAndDefaultRegistrationPageConfiguredThenNoDuplicateFilters() {
|
||||
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
|
||||
FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class);
|
||||
|
||||
List<DefaultResourcesFilter> defaultResourcesFilters = filterChain.getFilterChains()
|
||||
.get(0)
|
||||
.getFilters()
|
||||
.stream()
|
||||
.filter(DefaultResourcesFilter.class::isInstance)
|
||||
.map(DefaultResourcesFilter.class::cast)
|
||||
.toList();
|
||||
|
||||
assertThat(defaultResourcesFilters).map(DefaultResourcesFilter::toString)
|
||||
.filteredOn((filterDescription) -> filterDescription.contains("login/webauthn.js"))
|
||||
.hasSize(1);
|
||||
assertThat(defaultResourcesFilters).map(DefaultResourcesFilter::toString)
|
||||
.filteredOn((filterDescription) -> filterDescription.contains("default-ui.css"))
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception {
|
||||
this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire();
|
||||
this.mvc.perform(get("/login/webauthn.js"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string("content-type", "text/javascript;charset=UTF-8"))
|
||||
.andExpect(content().string(containsString("async function authenticate(")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webauthnWhenConfiguredAndNoDefaultRegistrationPageThenDoesNotServeJavascript() throws Exception {
|
||||
this.spring.register(NoDefaultRegistrationPageConfiguration.class).autowire();
|
||||
this.mvc.perform(get("/login/webauthn.js")).andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class DefaultWebauthnConfiguration {
|
||||
@@ -67,6 +135,22 @@ public class WebAuthnConfigurerTests {
|
||||
return new InMemoryUserDetailsManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.formLogin(Customizer.withDefaults()).webAuthn(Customizer.withDefaults()).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class NoFormLoginAndDefaultRegistrationPageConfiguration {
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.webAuthn(Customizer.withDefaults()).build();
|
||||
@@ -74,4 +158,40 @@ public class WebAuthnConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class FormLoginAndNoDefaultRegistrationPageConfiguration {
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.formLogin(Customizer.withDefaults())
|
||||
.webAuthn((webauthn) -> webauthn.disableDefaultRegistrationPage(true))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class NoDefaultRegistrationPageConfiguration {
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.formLogin((login) -> login.loginPage("/custom-login-page"))
|
||||
.webAuthn((webauthn) -> webauthn.disableDefaultRegistrationPage(true))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user