Browse Source

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
Rob Winch 8 years ago
parent
commit
f432c04111

+ 16 - 32
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<B extends ProviderManagerBuilder<B>, C
 	 * should provided. The remaining attributes have reasonable defaults.
 	 */
 	public class UserDetailsBuilder {
-		private String username;
-		private String password;
-		private List<GrantedAuthority> 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<B extends ProviderManagerBuilder<B>, 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<B extends ProviderManagerBuilder<B>, 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<B extends ProviderManagerBuilder<B>, C
 		 * additional attributes for this user)
 		 */
 		public UserDetailsBuilder roles(String... roles) {
-			List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(
-					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<B extends ProviderManagerBuilder<B>, 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<B extends ProviderManagerBuilder<B>, C
 		 * @see #roles(String...)
 		 */
 		public UserDetailsBuilder authorities(List<? extends GrantedAuthority> authorities) {
-			this.authorities = new ArrayList<GrantedAuthority>(authorities);
+			this.user.authorities(authorities);
 			return this;
 		}
 
@@ -208,7 +192,8 @@ public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, 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<B extends ProviderManagerBuilder<B>, 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<B extends ProviderManagerBuilder<B>, 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<B extends ProviderManagerBuilder<B>, 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<B extends ProviderManagerBuilder<B>, 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();
 		}
 	}
 }

+ 184 - 0
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<GrantedAuthority> 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:
+		 *
+		 * <code>
+		 *     builder.roles("USER","ADMIN");
+		 * </code>
+		 *
+		 * is equivalent to
+		 *
+		 * <code>
+		 *     builder.authorities("ROLE_USER","ROLE_ADMIN");
+		 * </code>
+		 *
+		 * <p>
+		 * This attribute is required, but can also be populated with
+		 * {@link #authorities(String...)}.
+		 * </p>
+		 *
+		 * @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<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(
+					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<? extends GrantedAuthority> authorities) {
+			this.authorities = new ArrayList<GrantedAuthority>(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);
+		}
+	}
 }

+ 3 - 0
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<UserDetails> users) {
 		for (UserDetails user : users) {
 			createUser(user);

+ 17 - 19
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

+ 10 - 8
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
 }