Bladeren bron

Add @EnableGlobalMultiFactorAuthentication

Closes gh-17954
Rob Winch 6 dagen geleden
bovenliggende
commit
f652920bb3

+ 65 - 0
config/src/main/java/org/springframework/security/config/annotation/authorization/EnableGlobalMultiFactorAuthentication.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2004-present 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.config.annotation.authorization;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Import;
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
+
+/**
+ * Exposes a {@link DefaultAuthorizationManagerFactory} as a Bean with the
+ * {@link #authorities()} specified as additional required authorities. The configuration
+ * will be picked up by both
+ * {@link org.springframework.security.config.annotation.web.configuration.EnableWebSecurity}
+ * and
+ * {@link org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity}.
+ *
+ * <pre>
+
+ * &#64;Configuration
+ * &#64;EnableGlobalMultiFactorAuthentication(authorities = { GrantedAuthorities.FACTOR_OTT, GrantedAuthorities.FACTOR_PASSWORD })
+ * public class MyConfiguration {
+ *     // ...
+ * }
+ * </pre>
+ *
+ * NOTE: At this time reactive applications do not support MFA and thus are not impacted.
+ * This will likely be enhanced in the future.
+ *
+ * @author Rob Winch
+ * @since 7.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+@Import(GlobalMultiFactorAuthenticationConfiguration.class)
+public @interface EnableGlobalMultiFactorAuthentication {
+
+	/**
+	 * The additional authorities that are required.
+	 * @return the additional authorities that are required (e.g. {
+	 * GrantedAuthorities.FACTOR_OTT, GrantedAuthorities.FACTOR_PASSWORD })
+	 * @see org.springframework.security.core.GrantedAuthorities
+	 */
+	String[] authorities();
+
+}

+ 56 - 0
config/src/main/java/org/springframework/security/config/annotation/authorization/GlobalMultiFactorAuthenticationConfiguration.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright 2004-present 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.config.annotation.authorization;
+
+import java.util.Map;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ImportAware;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
+
+/**
+ * Uses {@link EnableGlobalMultiFactorAuthentication} to configure a
+ * {@link DefaultAuthorizationManagerFactory}.
+ *
+ * @author Rob Winch
+ * @since 7.0
+ * @see EnableGlobalMultiFactorAuthentication
+ */
+class GlobalMultiFactorAuthenticationConfiguration implements ImportAware {
+
+	private String[] authorities;
+
+	@Bean
+	DefaultAuthorizationManagerFactory authorizationManagerFactory(ObjectProvider<RoleHierarchy> roleHierarchy) {
+		DefaultAuthorizationManagerFactory.Builder<Object> builder = DefaultAuthorizationManagerFactory.builder()
+			.requireAdditionalAuthorities(this.authorities);
+		roleHierarchy.ifAvailable(builder::roleHierarchy);
+		return builder.build();
+	}
+
+	@Override
+	public void setImportMetadata(AnnotationMetadata importMetadata) {
+		Map<String, Object> multiFactorAuthenticationAttrs = importMetadata
+			.getAnnotationAttributes(EnableGlobalMultiFactorAuthentication.class.getName());
+
+		this.authorities = (String[]) multiFactorAuthenticationAttrs.get("authorities");
+	}
+
+}

+ 123 - 0
config/src/test/java/org/springframework/security/config/annotation/authorization/EnableGlobalMultiFactorAuthenticationTests.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright 2004-present 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.config.annotation.authorization;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+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.security.access.AccessDeniedException;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.GrantedAuthorities;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Tests for {@link EnableGlobalMultiFactorAuthentication}.
+ *
+ * @author Rob Winch
+ */
+@ExtendWith(SpringExtension.class)
+@WebAppConfiguration
+public class EnableGlobalMultiFactorAuthenticationTests {
+
+	@Autowired
+	MockMvc mvc;
+
+	@Autowired
+	Service service;
+
+	@Test
+	@WithMockUser(
+			authorities = { GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY })
+	void webWhenAuthorized() throws Exception {
+		this.mvc.perform(get("/")).andExpect(status().isOk());
+	}
+
+	@Test
+	@WithMockUser
+	void webWhenNotAuthorized() throws Exception {
+		this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
+	}
+
+	@Test
+	@WithMockUser(
+			authorities = { GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY })
+	void methodWhenAuthorized() throws Exception {
+		Assertions.assertThatNoException().isThrownBy(() -> this.service.authenticated());
+	}
+
+	@Test
+	@WithMockUser
+	void methodWhenNotAuthorized() throws Exception {
+		Assertions.assertThatExceptionOfType(AccessDeniedException.class)
+			.isThrownBy(() -> this.service.authenticated());
+	}
+
+	@EnableWebSecurity
+	@EnableMethodSecurity
+	@Configuration
+	@EnableGlobalMultiFactorAuthentication(
+			authorities = { GrantedAuthorities.FACTOR_OTT_AUTHORITY, GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY })
+	static class Config {
+
+		@Bean
+		Service service() {
+			return new Service();
+		}
+
+		@Bean
+		MockMvc mvc(WebApplicationContext context) {
+			return MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
+		}
+
+		@RestController
+		static class OkController {
+
+			@GetMapping("/")
+			String ok() {
+				return "ok";
+			}
+
+		}
+
+	}
+
+	static class Service {
+
+		@PreAuthorize("isAuthenticated()")
+		void authenticated() {
+		}
+
+	}
+
+}

+ 8 - 0
docs/modules/ROOT/pages/servlet/authentication/adaptive.adoc

@@ -62,6 +62,14 @@ This yields a more familiar configuration:
 
 include-code::./UseAuthorizationManagerFactoryConfiguration[tag=httpSecurity,indent=0]
 
+[[enable-global-mfa]]
+=== @EnableGlobalMultiFactorAuthentication
+
+You can simplify the configuration even further by using `@EnableGlobalMultiFactorAuthentication` to create the `AuthorizationManagerFactory` for you.
+
+include-code::./EnableGlobalMultiFactorAuthenticationConfiguration[tag=enable-global-mfa,indent=0]
+
+
 [[obtaining-more-authorization]]
 == Authorizing More Scopes
 

+ 58 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationConfiguration.java

@@ -0,0 +1,58 @@
+package org.springframework.security.docs.servlet.authentication.enableglobalmfa;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.authorization.EnableGlobalMultiFactorAuthentication;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.GrantedAuthorities;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
+import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
+
+@EnableWebSecurity
+@Configuration(proxyBeanMethods = false)
+// tag::enable-global-mfa[]
+@EnableGlobalMultiFactorAuthentication(authorities = {
+		GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
+		GrantedAuthorities.FACTOR_OTT_AUTHORITY })
+// end::enable-global-mfa[]
+public class EnableGlobalMultiFactorAuthenticationConfiguration {
+
+	// tag::httpSecurity[]
+	@Bean
+	SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+		// @formatter:off
+		http
+				.authorizeHttpRequests((authorize) -> authorize
+						.requestMatchers("/admin/**").hasRole("ADMIN")
+						.anyRequest().authenticated()
+				)
+				.formLogin(Customizer.withDefaults())
+				.oneTimeTokenLogin(Customizer.withDefaults());
+		// @formatter:on
+		return http.build();
+	}
+	// end::httpSecurity[]
+
+	@Bean
+	UserDetailsService userDetailsService() {
+		return new InMemoryUserDetailsManager(
+				User.withDefaultPasswordEncoder()
+						.username("user")
+						.password("password")
+						.authorities("app")
+						.build()
+		);
+	}
+
+	@Bean
+	OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler() {
+		return new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
+	}
+}
+

+ 115 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationTests.java

@@ -0,0 +1,115 @@
+/*
+ * Copyright 2004-present 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.docs.servlet.authentication.enableglobalmfa;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.config.test.SpringTestContext;
+import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.core.GrantedAuthorities;
+import org.springframework.security.docs.servlet.authentication.servletx509config.CustomX509Configuration;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Tests {@link CustomX509Configuration}.
+ *
+ * @author Rob Winch
+ */
+@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class })
+@TestExecutionListeners(WithSecurityContextTestExecutionListener.class)
+public class EnableGlobalMultiFactorAuthenticationTests {
+
+	public final SpringTestContext spring = new SpringTestContext(this);
+
+	@Autowired
+	MockMvc mockMvc;
+
+	@Test
+	@WithMockUser(authorities = { GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY })
+	void getWhenAuthenticatedWithPasswordAndOttThenPermits() throws Exception {
+		this.spring.register(EnableGlobalMultiFactorAuthenticationConfiguration.class, Http200Controller.class).autowire();
+		// @formatter:off
+		this.mockMvc.perform(get("/"))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withUsername("user"));
+		// @formatter:on
+	}
+
+	@Test
+	@WithMockUser(authorities = GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY)
+	void getWhenAuthenticatedWithPasswordThenRedirectsToOtt() throws Exception {
+		this.spring.register(EnableGlobalMultiFactorAuthenticationConfiguration.class, Http200Controller.class).autowire();
+		// @formatter:off
+		this.mockMvc.perform(get("/"))
+			.andExpect(status().is3xxRedirection())
+			.andExpect(redirectedUrl("http://localhost/login?factor=ott"));
+		// @formatter:on
+	}
+
+	@Test
+	@WithMockUser(authorities = GrantedAuthorities.FACTOR_OTT_AUTHORITY)
+	void getWhenAuthenticatedWithOttThenRedirectsToPassword() throws Exception {
+		this.spring.register(EnableGlobalMultiFactorAuthenticationConfiguration.class, Http200Controller.class).autowire();
+		// @formatter:off
+		this.mockMvc.perform(get("/"))
+			.andExpect(status().is3xxRedirection())
+			.andExpect(redirectedUrl("http://localhost/login?factor=password"));
+		// @formatter:on
+	}
+
+	@Test
+	@WithMockUser
+	void getWhenAuthenticatedThenRedirectsToPassword() throws Exception {
+		this.spring.register(EnableGlobalMultiFactorAuthenticationConfiguration.class, Http200Controller.class).autowire();
+		// @formatter:off
+		this.mockMvc.perform(get("/"))
+			.andExpect(status().is3xxRedirection())
+			.andExpect(redirectedUrl("http://localhost/login?factor=password"));
+		// @formatter:on
+	}
+
+	@Test
+	void getWhenUnauthenticatedThenRedirectsToBoth() throws Exception {
+		this.spring.register(EnableGlobalMultiFactorAuthenticationConfiguration.class, Http200Controller.class).autowire();
+		// @formatter:off
+		this.mockMvc.perform(get("/"))
+			.andExpect(status().is3xxRedirection())
+			.andExpect(redirectedUrl("http://localhost/login"));
+		// @formatter:on
+	}
+
+	@RestController
+	static class Http200Controller {
+		@GetMapping("/**")
+		String ok() {
+			return "ok";
+		}
+	}
+}

+ 120 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/AuthorizationManagerFactoryTests.kt

@@ -0,0 +1,120 @@
+/*
+ * Copyright 2004-present 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.kt.docs.servlet.authentication.enableglobalmfa
+
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.test.SpringTestContext
+import org.springframework.security.config.test.SpringTestContextExtension
+import org.springframework.security.core.GrantedAuthorities
+import org.springframework.security.test.context.support.WithMockUser
+import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener
+import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
+import org.springframework.test.context.TestExecutionListeners
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+
+/**
+ * Tests [CustomX509Configuration].
+ *
+ * @author Rob Winch
+ */
+@ExtendWith(SpringExtension::class, SpringTestContextExtension::class)
+@TestExecutionListeners(WithSecurityContextTestExecutionListener::class)
+class AuthorizationManagerFactoryTests {
+    @JvmField
+    val spring: SpringTestContext = SpringTestContext(this)
+
+    @Autowired
+    var mockMvc: MockMvc? = null
+
+    @Test
+    @WithMockUser(authorities = [GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY])
+    @Throws(Exception::class)
+    fun getWhenAuthenticatedWithPasswordAndOttThenPermits() {
+        this.spring.register(UseAuthorizationManagerFactoryConfiguration::class.java, Http200Controller::class.java)
+            .autowire()
+        // @formatter:off
+        this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
+        .andExpect(MockMvcResultMatchers.status().isOk())
+        .andExpect(SecurityMockMvcResultMatchers.authenticated().withUsername("user"))
+    		// @formatter:on
+    }
+
+    @Test
+    @WithMockUser(authorities = [GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY])
+    @Throws(Exception::class)
+    fun getWhenAuthenticatedWithPasswordThenRedirectsToOtt() {
+        this.spring.register(UseAuthorizationManagerFactoryConfiguration::class.java, Http200Controller::class.java)
+            .autowire()
+        // @formatter:off
+        this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
+        .andExpect(MockMvcResultMatchers.status().is3xxRedirection())
+        .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=ott"))
+    		// @formatter:on
+    }
+
+    @Test
+    @WithMockUser(authorities = [GrantedAuthorities.FACTOR_OTT_AUTHORITY])
+    @Throws(Exception::class)
+    fun getWhenAuthenticatedWithOttThenRedirectsToPassword() {
+        this.spring.register(UseAuthorizationManagerFactoryConfiguration::class.java, Http200Controller::class.java)
+            .autowire()
+        // @formatter:off
+        this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
+        .andExpect(MockMvcResultMatchers.status().is3xxRedirection())
+        .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=password"))
+    		// @formatter:on
+    }
+
+    @Test
+    @WithMockUser
+    @Throws(Exception::class)
+    fun getWhenAuthenticatedThenRedirectsToPassword() {
+        this.spring.register(UseAuthorizationManagerFactoryConfiguration::class.java, Http200Controller::class.java)
+            .autowire()
+        // @formatter:off
+        this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
+        .andExpect(MockMvcResultMatchers.status().is3xxRedirection())
+        .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=password"))
+    		// @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getWhenUnauthenticatedThenRedirectsToBoth() {
+        this.spring.register(UseAuthorizationManagerFactoryConfiguration::class.java, Http200Controller::class.java)
+            .autowire()
+        // @formatter:off
+        this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
+        .andExpect(MockMvcResultMatchers.status().is3xxRedirection())
+        .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login"))
+    		// @formatter:on
+    }
+
+    @RestController
+    internal class Http200Controller {
+        @GetMapping("/**")
+        fun ok(): String {
+            return "ok"
+        }
+    }
+}

+ 54 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/ListAuthoritiesEverywhereConfiguration.kt

@@ -0,0 +1,54 @@
+package org.springframework.security.kt.docs.servlet.authentication.enableglobalmfa
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+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.invoke
+import org.springframework.security.core.GrantedAuthorities
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+import org.springframework.security.web.SecurityFilterChain
+import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
+import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
+
+@EnableWebSecurity
+@Configuration(proxyBeanMethods = false)
+class ListAuthoritiesEverywhereConfiguration {
+
+    // tag::httpSecurity[]
+    @Bean
+    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
+        // @formatter:off
+        http {
+            authorizeHttpRequests {
+                authorize("/admin/**", hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_ADMIN")) // <1>
+                authorize(anyRequest, hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY))
+            }
+            formLogin { }
+            oneTimeTokenLogin {  }
+        }
+        // @formatter:on
+        return http.build()
+    }
+    // end::httpSecurity[]
+
+
+    // end::httpSecurity[]
+    @Bean
+    fun userDetailsService(): UserDetailsService {
+        return InMemoryUserDetailsManager(
+            User.withDefaultPasswordEncoder()
+                .username("user")
+                .password("password")
+                .authorities("app")
+                .build()
+        )
+    }
+
+    @Bean
+    fun tokenGenerationSuccessHandler(): OneTimeTokenGenerationSuccessHandler {
+        return RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent")
+    }
+}

+ 61 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/UseAuthorizationManagerFactoryConfiguration.kt

@@ -0,0 +1,61 @@
+package org.springframework.security.kt.docs.servlet.authentication.enableglobalmfa
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.authorization.AuthorizationManagerFactory
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
+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.invoke
+import org.springframework.security.core.GrantedAuthorities
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+import org.springframework.security.web.SecurityFilterChain
+import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
+import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
+
+@EnableWebSecurity
+@Configuration(proxyBeanMethods = false)
+internal class UseAuthorizationManagerFactoryConfiguration {
+    // tag::httpSecurity[]
+    @Bean
+    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
+        // @formatter:off
+        http {
+            authorizeHttpRequests {
+                authorize("/admin/**", hasRole("ADMIN"))
+                authorize(anyRequest, authenticated)
+            }
+            formLogin { }
+            oneTimeTokenLogin { }
+        }
+        // @formatter:on
+        return http.build()
+    }
+    // end::httpSecurity[]
+
+    // tag::authorizationManagerFactoryBean[]
+    @Bean
+    fun authz(): AuthorizationManagerFactory<Object> {
+        return DefaultAuthorizationManagerFactory.builder<Object>()
+            .requireAdditionalAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY).build()
+    }
+    // end::authorizationManagerFactoryBean[]
+
+    @Bean
+    fun userDetailsService(): UserDetailsService {
+        return InMemoryUserDetailsManager(
+            User.withDefaultPasswordEncoder()
+                .username("user")
+                .password("password")
+                .authorities("app")
+                .build()
+        )
+    }
+
+    @Bean
+    fun tokenGenerationSuccessHandler(): OneTimeTokenGenerationSuccessHandler {
+        return RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent")
+    }
+}