mirror of
https://github.com/spring-projects/spring-security.git
synced 2026-03-30 14:08:11 +00:00
Support Customizer<AdditionalRequiredFactorsBuilder<Object>>>
Closes gh-18922
This commit is contained in:
parent
c71b178f63
commit
bd7171140e
@ -16,13 +16,16 @@
|
||||
|
||||
package org.springframework.security.config.annotation.authorization;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactories;
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder;
|
||||
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
|
||||
import org.springframework.security.config.Customizer;
|
||||
|
||||
/**
|
||||
* Uses {@link EnableMultiFactorAuthentication} to configure a
|
||||
@ -37,10 +40,13 @@ class AuthorizationManagerFactoryConfiguration implements ImportAware {
|
||||
private String[] authorities;
|
||||
|
||||
@Bean
|
||||
DefaultAuthorizationManagerFactory authorizationManagerFactory() {
|
||||
AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object> builder = AuthorizationManagerFactories
|
||||
.multiFactor()
|
||||
DefaultAuthorizationManagerFactory authorizationManagerFactory(
|
||||
List<Customizer<AdditionalRequiredFactorsBuilder<Object>>> additionalRequiredFactorsCustomizers) {
|
||||
AdditionalRequiredFactorsBuilder<Object> builder = AuthorizationManagerFactories.multiFactor()
|
||||
.requireFactors(this.authorities);
|
||||
for (Customizer<AdditionalRequiredFactorsBuilder<Object>> customizer : additionalRequiredFactorsCustomizers) {
|
||||
customizer.customize(builder);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
@ -45,6 +45,19 @@ import org.springframework.security.authorization.DefaultAuthorizationManagerFac
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* You can also publish one or more
|
||||
* {@code Customizer<AdditionalRequiredFactorsBuilder<Object>>} beans to further customize
|
||||
* the {@link DefaultAuthorizationManagerFactory}. For example, conditionally applying MFA
|
||||
* for specific users:
|
||||
*
|
||||
* <pre>
|
||||
* @Bean
|
||||
* Customizer<AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object>> additionalRequiredFactorsCustomizer() {
|
||||
* return (builder) -> builder.when((auth) -> "admin".equals(auth.getName()));
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* NOTE: At this time reactive applications do not support MFA and thus are not impacted.
|
||||
* This will likely be enhanced in the future.
|
||||
*
|
||||
|
||||
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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.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.authorization.AuthorizationManagerFactories;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
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 EnableMultiFactorAuthentication} with
|
||||
* {@link Customizer}<{@link AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder}>.
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WebAppConfiguration
|
||||
@ContextConfiguration(classes = EnableMultiFactorAuthenticationCustomizerTests.ConfigWithCustomizer.class)
|
||||
class EnableMultiFactorAuthenticationCustomizerTests {
|
||||
|
||||
@Autowired
|
||||
MockMvc mvc;
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "user", authorities = "ROLE_USER")
|
||||
void whenCustomizerAppliedThenConditionalMfaUsed() throws Exception {
|
||||
this.mvc.perform(get("/")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "admin", authorities = "ROLE_USER")
|
||||
void whenCustomizerAppliedAndConditionTrueThenMfaRequired() throws Exception {
|
||||
this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "admin", authorities = { "ROLE_USER", FactorGrantedAuthority.PASSWORD_AUTHORITY,
|
||||
FactorGrantedAuthority.OTT_AUTHORITY })
|
||||
void whenCustomizerAppliedAndConditionTrueWithMfaThenAuthorized() throws Exception {
|
||||
this.mvc.perform(get("/")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableMultiFactorAuthentication(
|
||||
authorities = { FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY })
|
||||
static class ConfigWithCustomizer {
|
||||
|
||||
@Bean
|
||||
Customizer<AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object>> additionalRequiredFactorsCustomizer() {
|
||||
return (builder) -> builder.when((auth) -> "admin".equals(auth.getName()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
MockMvc mvc(WebApplicationContext context) {
|
||||
return MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("deprecation")
|
||||
UserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
UserDetails admin = User.withDefaultPasswordEncoder()
|
||||
.username("admin")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user, admin);
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class OkController {
|
||||
|
||||
@GetMapping("/")
|
||||
String ok() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.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.core.annotation.Order;
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactories;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
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 EnableMultiFactorAuthentication} with multiple
|
||||
* {@link Customizer}<{@link AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder}>
|
||||
* beans.
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WebAppConfiguration
|
||||
@ContextConfiguration(
|
||||
classes = EnableMultiFactorAuthenticationMultipleCustomizersTests.ConfigWithMultipleCustomizers.class)
|
||||
class EnableMultiFactorAuthenticationMultipleCustomizersTests {
|
||||
|
||||
@Autowired
|
||||
MockMvc mvc;
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "user", authorities = "ROLE_USER")
|
||||
void whenCustomizerAppliedThenConditionalMfaUsed() throws Exception {
|
||||
this.mvc.perform(get("/")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "admin", authorities = "ROLE_USER")
|
||||
void whenCustomizersAppliedAndConditionTrueThenMfaRequired() throws Exception {
|
||||
this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "admin", authorities = { "ROLE_USER", FactorGrantedAuthority.PASSWORD_AUTHORITY,
|
||||
FactorGrantedAuthority.OTT_AUTHORITY })
|
||||
void whenCustomizersAppliedAndConditionTrueWithMfaThenAuthorized() throws Exception {
|
||||
this.mvc.perform(get("/")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "manager", authorities = "ROLE_USER")
|
||||
void whenSecondCustomizerAppliedAndConditionTrueThenMfaRequired() throws Exception {
|
||||
this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableMultiFactorAuthentication(
|
||||
authorities = { FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY })
|
||||
static class ConfigWithMultipleCustomizers {
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
Customizer<AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object>> adminCustomizer() {
|
||||
return (builder) -> builder.withWhen(
|
||||
(current) -> (auth) -> "admin".equals(auth.getName()) || (current != null && current.test(auth)));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
Customizer<AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object>> managerCustomizer() {
|
||||
return (builder) -> builder.withWhen(
|
||||
(current) -> (auth) -> "manager".equals(auth.getName()) || (current != null && current.test(auth)));
|
||||
}
|
||||
|
||||
@Bean
|
||||
MockMvc mvc(WebApplicationContext context) {
|
||||
return MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("deprecation")
|
||||
UserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
UserDetails admin = User.withDefaultPasswordEncoder()
|
||||
.username("admin")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
UserDetails manager = User.withDefaultPasswordEncoder()
|
||||
.username("manager")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user, admin, manager);
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class OkController {
|
||||
|
||||
@GetMapping("/")
|
||||
String ok() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -37,6 +37,14 @@ Spring Security behind the scenes knows which endpoint to go to depending on whi
|
||||
If the user logged in initially with their username and password, then Spring Security redirects to the One-Time-Token Login page.
|
||||
If the user logged in initially with a token, then Spring Security redirects to the Username/Password Login page.
|
||||
|
||||
[[mfa-when-custom-conditions]]
|
||||
=== Custom MFA Conditions
|
||||
|
||||
You can also publish one or more `Customizer<AdditionalRequiredFactorsBuilder<Object>>` beans to customize the factory created by `@EnableMultiFactorAuthentication`.
|
||||
For example, you can conditionally apply MFA for specific users:
|
||||
|
||||
include-code::./CustomizerAuthorizationManagerFactoryConfiguration[tag=customizer,indent=0]
|
||||
|
||||
[[authorization-manager-factory]]
|
||||
== AuthorizationManagerFactory
|
||||
|
||||
@ -48,6 +56,7 @@ The `AuthorizationManagerFactory` Bean below is what is published in the previou
|
||||
|
||||
include-code::./UseAuthorizationManagerFactoryConfiguration[tag=authorizationManagerFactoryBean,indent=0]
|
||||
|
||||
|
||||
[[selective-mfa]]
|
||||
== Selectively Requiring MFA
|
||||
|
||||
|
||||
@ -47,6 +47,13 @@ class UseAuthorizationManagerFactoryConfiguration {
|
||||
}
|
||||
// end::authorizationManagerFactoryBean[]
|
||||
|
||||
// tag::customizer[]
|
||||
@Bean
|
||||
Customizer<AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object>> additionalRequiredFactorsCustomizer() {
|
||||
return (builder) -> builder.when((auth) -> "admin".equals(auth.getName()));
|
||||
}
|
||||
// end::customizer[]
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager(
|
||||
|
||||
@ -2,6 +2,7 @@ package org.springframework.security.docs.servlet.authentication.emfa;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactories;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
|
||||
@ -4,6 +4,7 @@ import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactories
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactory
|
||||
import org.springframework.security.config.Customizer
|
||||
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
|
||||
@ -47,6 +48,13 @@ internal class UseAuthorizationManagerFactoryConfiguration {
|
||||
}
|
||||
// end::authorizationManagerFactoryBean[]
|
||||
|
||||
// tag::customizer[]
|
||||
@Bean
|
||||
fun additionalRequiredFactorsCustomizer(): Customizer<AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Any>> {
|
||||
return Customizer { builder -> builder.`when` { auth -> "admin" == auth.name } }
|
||||
}
|
||||
// end::customizer[]
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Bean
|
||||
fun userDetailsService(): UserDetailsService {
|
||||
|
||||
@ -2,6 +2,8 @@ package org.springframework.security.kt.docs.servlet.authentication.emfa
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactories
|
||||
import org.springframework.security.config.Customizer
|
||||
import org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user