diff --git a/config/src/main/java/org/springframework/security/config/annotation/authorization/EnableGlobalMultiFactorAuthentication.java b/config/src/main/java/org/springframework/security/config/annotation/authorization/EnableGlobalMultiFactorAuthentication.java new file mode 100644 index 0000000000..ddc5840442 --- /dev/null +++ b/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}. + * + *
+
+ * @Configuration
+ * @EnableGlobalMultiFactorAuthentication(authorities = { GrantedAuthorities.FACTOR_OTT, GrantedAuthorities.FACTOR_PASSWORD })
+ * public class MyConfiguration {
+ *     // ...
+ * }
+ * 
+ * + * 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(); + +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/authorization/GlobalMultiFactorAuthenticationConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/authorization/GlobalMultiFactorAuthenticationConfiguration.java new file mode 100644 index 0000000000..7ebf32f261 --- /dev/null +++ b/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) { + DefaultAuthorizationManagerFactory.Builder builder = DefaultAuthorizationManagerFactory.builder() + .requireAdditionalAuthorities(this.authorities); + roleHierarchy.ifAvailable(builder::roleHierarchy); + return builder.build(); + } + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + Map multiFactorAuthenticationAttrs = importMetadata + .getAnnotationAttributes(EnableGlobalMultiFactorAuthentication.class.getName()); + + this.authorities = (String[]) multiFactorAuthenticationAttrs.get("authorities"); + } + +} diff --git a/config/src/test/java/org/springframework/security/config/annotation/authorization/EnableGlobalMultiFactorAuthenticationTests.java b/config/src/test/java/org/springframework/security/config/annotation/authorization/EnableGlobalMultiFactorAuthenticationTests.java new file mode 100644 index 0000000000..b38a6f43e4 --- /dev/null +++ b/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() { + } + + } + +} diff --git a/docs/modules/ROOT/pages/servlet/authentication/adaptive.adoc b/docs/modules/ROOT/pages/servlet/authentication/adaptive.adoc index ef44b909c0..22446f4776 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/adaptive.adoc +++ b/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 diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationConfiguration.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationConfiguration.java new file mode 100644 index 0000000000..eb569078dc --- /dev/null +++ b/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"); + } +} + diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationTests.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationTests.java new file mode 100644 index 0000000000..70dcb737a2 --- /dev/null +++ b/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"; + } + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/AuthorizationManagerFactoryTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/AuthorizationManagerFactoryTests.kt new file mode 100644 index 0000000000..eea878f2da --- /dev/null +++ b/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" + } + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/ListAuthoritiesEverywhereConfiguration.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/ListAuthoritiesEverywhereConfiguration.kt new file mode 100644 index 0000000000..7c00ede58d --- /dev/null +++ b/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") + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/UseAuthorizationManagerFactoryConfiguration.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/UseAuthorizationManagerFactoryConfiguration.kt new file mode 100644 index 0000000000..f037dd8023 --- /dev/null +++ b/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 { + return DefaultAuthorizationManagerFactory.builder() + .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") + } +}