From f432c041116e0bdcd390ffef739f323cbb8af336 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Fri, 21 Oct 2016 16:42:03 -0500 Subject: [PATCH] Create UserBuilder This commit creates a UserBuilder and updates samples to use it. We do not leverate it for JdbcUserDetailsManager because it requires the schema to be created which is difficult with a single bean definition and unpredicatble ordering. For this, it is still advised to use AuthenticationManagerBuilder Fixes gh-4095 --- .../UserDetailsManagerConfigurer.java | 48 ++--- .../security/core/userdetails/User.java | 184 ++++++++++++++++++ .../InMemoryUserDetailsManager.java | 3 + docs/manual/src/docs/asciidoc/index.adoc | 36 ++-- .../samples/config/SecurityConfig.java | 18 +- 5 files changed, 230 insertions(+), 59 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/provisioning/UserDetailsManagerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/provisioning/UserDetailsManagerConfigurer.java index 0f413fb921..d353534da5 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/provisioning/UserDetailsManagerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/provisioning/UserDetailsManagerConfigurer.java @@ -16,19 +16,16 @@ package org.springframework.security.config.annotation.authentication.configurers.provisioning; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.springframework.security.config.annotation.SecurityBuilder; import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder; import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsServiceConfigurer; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.User.UserBuilder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.provisioning.UserDetailsManager; -import org.springframework.util.Assert; /** * Base class for populating an @@ -82,13 +79,7 @@ public class UserDetailsManagerConfigurer, C * should provided. The remaining attributes have reasonable defaults. */ public class UserDetailsBuilder { - private String username; - private String password; - private List authorities; - private boolean accountExpired; - private boolean accountLocked; - private boolean credentialsExpired; - private boolean disabled; + private UserBuilder user; private final C builder; /** @@ -117,8 +108,7 @@ public class UserDetailsManagerConfigurer, C * additional attributes for this user) */ private UserDetailsBuilder username(String username) { - Assert.notNull(username, "username cannot be null"); - this.username = username; + this.user = User.withUsername(username); return this; } @@ -130,8 +120,7 @@ public class UserDetailsManagerConfigurer, C * additional attributes for this user) */ public UserDetailsBuilder password(String password) { - Assert.notNull(password, "password cannot be null"); - this.password = password; + this.user.password(password); return this; } @@ -161,14 +150,8 @@ public class UserDetailsManagerConfigurer, C * additional attributes for this user) */ public UserDetailsBuilder roles(String... roles) { - List authorities = new ArrayList( - roles.length); - for (String role : roles) { - Assert.isTrue(!role.startsWith("ROLE_"), role - + " cannot start with ROLE_ (it is automatically added)"); - authorities.add(new SimpleGrantedAuthority("ROLE_" + role)); - } - return authorities(authorities); + this.user.roles(roles); + return this; } /** @@ -181,7 +164,8 @@ public class UserDetailsManagerConfigurer, C * @see #roles(String...) */ public UserDetailsBuilder authorities(GrantedAuthority... authorities) { - return authorities(Arrays.asList(authorities)); + this.user.authorities(authorities); + return this; } /** @@ -194,7 +178,7 @@ public class UserDetailsManagerConfigurer, C * @see #roles(String...) */ public UserDetailsBuilder authorities(List authorities) { - this.authorities = new ArrayList(authorities); + this.user.authorities(authorities); return this; } @@ -208,7 +192,8 @@ public class UserDetailsManagerConfigurer, C * @see #roles(String...) */ public UserDetailsBuilder authorities(String... authorities) { - return authorities(AuthorityUtils.createAuthorityList(authorities)); + this.user.authorities(authorities); + return this; } /** @@ -219,7 +204,7 @@ public class UserDetailsManagerConfigurer, C * additional attributes for this user) */ public UserDetailsBuilder accountExpired(boolean accountExpired) { - this.accountExpired = accountExpired; + this.user.accountExpired(accountExpired); return this; } @@ -231,7 +216,7 @@ public class UserDetailsManagerConfigurer, C * additional attributes for this user) */ public UserDetailsBuilder accountLocked(boolean accountLocked) { - this.accountLocked = accountLocked; + this.user.accountLocked(accountLocked); return this; } @@ -243,7 +228,7 @@ public class UserDetailsManagerConfigurer, C * additional attributes for this user) */ public UserDetailsBuilder credentialsExpired(boolean credentialsExpired) { - this.credentialsExpired = credentialsExpired; + this.user.credentialsExpired(credentialsExpired); return this; } @@ -255,13 +240,12 @@ public class UserDetailsManagerConfigurer, C * additional attributes for this user) */ public UserDetailsBuilder disabled(boolean disabled) { - this.disabled = disabled; + this.user.disabled(disabled); return this; } private UserDetails build() { - return new User(username, password, !disabled, !accountExpired, - !credentialsExpired, !accountLocked, authorities); + return this.user.build(); } } } diff --git a/core/src/main/java/org/springframework/security/core/userdetails/User.java b/core/src/main/java/org/springframework/security/core/userdetails/User.java index edebe0aea3..eb8cb53d85 100644 --- a/core/src/main/java/org/springframework/security/core/userdetails/User.java +++ b/core/src/main/java/org/springframework/security/core/userdetails/User.java @@ -17,9 +17,12 @@ package org.springframework.security.core.userdetails; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @@ -27,6 +30,8 @@ import java.util.TreeSet; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.CredentialsContainer; import org.springframework.security.core.SpringSecurityCoreVersion; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.util.Assert; /** @@ -238,4 +243,183 @@ public class User implements UserDetails, CredentialsContainer { return sb.toString(); } + + public static UserBuilder withUsername(String username) { + return new UserBuilder().username(username); + } + + /** + * Builds the user to be added. At minimum the username, password, and authorities + * should provided. The remaining attributes have reasonable defaults. + */ + public static class UserBuilder { + private String username; + private String password; + private List authorities; + private boolean accountExpired; + private boolean accountLocked; + private boolean credentialsExpired; + private boolean disabled; + + /** + * Creates a new instance + */ + private UserBuilder() { + } + + /** + * Populates the username. This attribute is required. + * + * @param username the username. Cannot be null. + * @return the {@link UserBuilder} for method chaining (i.e. to populate + * additional attributes for this user) + */ + private UserBuilder username(String username) { + Assert.notNull(username, "username cannot be null"); + this.username = username; + return this; + } + + /** + * Populates the password. This attribute is required. + * + * @param password the password. Cannot be null. + * @return the {@link UserBuilder} for method chaining (i.e. to populate + * additional attributes for this user) + */ + public UserBuilder password(String password) { + Assert.notNull(password, "password cannot be null"); + this.password = password; + return this; + } + + /** + * Populates the roles. This method is a shortcut for calling + * {@link #authorities(String...)}, but automatically prefixes each entry with + * "ROLE_". This means the following: + * + * + * builder.roles("USER","ADMIN"); + * + * + * is equivalent to + * + * + * builder.authorities("ROLE_USER","ROLE_ADMIN"); + * + * + *

+ * This attribute is required, but can also be populated with + * {@link #authorities(String...)}. + *

+ * + * @param roles the roles for this user (i.e. USER, ADMIN, etc). Cannot be null, + * contain null values or start with "ROLE_" + * @return the {@link UserBuilder} for method chaining (i.e. to populate + * additional attributes for this user) + */ + public UserBuilder roles(String... roles) { + List authorities = new ArrayList( + roles.length); + for (String role : roles) { + Assert.isTrue(!role.startsWith("ROLE_"), role + + " cannot start with ROLE_ (it is automatically added)"); + authorities.add(new SimpleGrantedAuthority("ROLE_" + role)); + } + return authorities(authorities); + } + + /** + * Populates the authorities. This attribute is required. + * + * @param authorities the authorities for this user. Cannot be null, or contain + * null values + * @return the {@link UserBuilder} for method chaining (i.e. to populate + * additional attributes for this user) + * @see #roles(String...) + */ + public UserBuilder authorities(GrantedAuthority... authorities) { + return authorities(Arrays.asList(authorities)); + } + + /** + * Populates the authorities. This attribute is required. + * + * @param authorities the authorities for this user. Cannot be null, or contain + * null values + * @return the {@link UserBuilder} for method chaining (i.e. to populate + * additional attributes for this user) + * @see #roles(String...) + */ + public UserBuilder authorities(List authorities) { + this.authorities = new ArrayList(authorities); + return this; + } + + /** + * Populates the authorities. This attribute is required. + * + * @param authorities the authorities for this user (i.e. ROLE_USER, ROLE_ADMIN, + * etc). Cannot be null, or contain null values + * @return the {@link UserBuilder} for method chaining (i.e. to populate + * additional attributes for this user) + * @see #roles(String...) + */ + public UserBuilder authorities(String... authorities) { + return authorities(AuthorityUtils.createAuthorityList(authorities)); + } + + /** + * Defines if the account is expired or not. Default is false. + * + * @param accountExpired true if the account is expired, false otherwise + * @return the {@link UserBuilder} for method chaining (i.e. to populate + * additional attributes for this user) + */ + public UserBuilder accountExpired(boolean accountExpired) { + this.accountExpired = accountExpired; + return this; + } + + /** + * Defines if the account is locked or not. Default is false. + * + * @param accountLocked true if the account is locked, false otherwise + * @return the {@link UserBuilder} for method chaining (i.e. to populate + * additional attributes for this user) + */ + public UserBuilder accountLocked(boolean accountLocked) { + this.accountLocked = accountLocked; + return this; + } + + /** + * Defines if the credentials are expired or not. Default is false. + * + * @param credentialsExpired true if the credentials are expired, false otherwise + * @return the {@link UserBuilder} for method chaining (i.e. to populate + * additional attributes for this user) + */ + public UserBuilder credentialsExpired(boolean credentialsExpired) { + this.credentialsExpired = credentialsExpired; + return this; + } + + /** + * Defines if the account is disabled or not. Default is false. + * + * @param disabled true if the account is disabled, false otherwise + * @return the {@link UserBuilder} for method chaining (i.e. to populate + * additional attributes for this user) + */ + public UserBuilder disabled(boolean disabled) { + this.disabled = disabled; + return this; + } + + public UserDetails build() { + return new User(username, password, !disabled, !accountExpired, + !credentialsExpired, !accountLocked, authorities); + } + } } diff --git a/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java b/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java index 8270033718..9d884125ee 100644 --- a/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java +++ b/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java @@ -52,6 +52,9 @@ public class InMemoryUserDetailsManager implements UserDetailsManager { private AuthenticationManager authenticationManager; + public InMemoryUserDetailsManager() { + } + public InMemoryUserDetailsManager(Collection users) { for (UserDetails user : users) { createUser(user); diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index 98437a2c21..e2a74b1b5b 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -459,17 +459,15 @@ import org.springframework.security.config.annotation.web.configuration.*; @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER"); + @Bean + public UserDetailsService userDetailsService() throws Exception { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + manager.createUser(User.withUsername("user").password("password").roles("USER").build()); + return manager; } } ---- -NOTE: The name of the configureGlobal method is not important. However, it is important to only configure AuthenticationManagerBuilder in a class annotated with either `@EnableWebSecurity`, `@EnableGlobalMethodSecurity`, or `@EnableGlobalAuthentication`. Doing otherwise has unpredictable results. - There really isn't much to this configuration, but it does a lot. You can find a summary of the features below: * Require authentication to every URL in your application @@ -798,12 +796,12 @@ We have already seen an example of configuring in memory authentication for a si [source,java] ---- -@Autowired -public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER").and() - .withUser("admin").password("password").roles("USER", "ADMIN"); +@Bean +public UserDetailsService userDetailsService() throws Exception { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + manager.createUser(User.withUsername("user").password("password").roles("USER").build()); + manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build()); + return manager; } ---- @@ -947,12 +945,12 @@ We can configure multiple HttpSecurity instances just as we can have multiple `< ---- @EnableWebSecurity public class MultiHttpSecurityConfig { - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) { <1> - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER").and() - .withUser("admin").password("password").roles("USER", "ADMIN"); + @Bean + public UserDetailsService userDetailsService() throws Exception { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + manager.createUser(User.withUsername("user").password("password").roles("USER").build()); + manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build()); + return manager; } @Configuration diff --git a/samples/javaconfig/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index f5ad79d29c..34ec8888d6 100644 --- a/samples/javaconfig/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -15,19 +15,21 @@ */ package org.springframework.security.samples.config; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.config.annotation.authentication.builders.*; -import org.springframework.security.config.annotation.web.configuration.*; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; @EnableWebSecurity public class SecurityConfig { // @formatter:off - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER"); + @Bean + public UserDetailsService userDetailsService() throws Exception { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + manager.createUser(User.withUsername("user").password("password").roles("USER").build()); + return manager; } // @formatter:on }