Explorar el Código

Add UserBuilder Methods

Fixes: gh-5303
Rob Winch hace 7 años
padre
commit
544e421157

+ 118 - 4
core/src/main/java/org/springframework/security/core/userdetails/User.java

@@ -27,11 +27,16 @@ import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 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.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.NoOpPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.util.Assert;
 
 /**
@@ -58,6 +63,8 @@ public class User implements UserDetails, CredentialsContainer {
 
 	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
 
+	private static final Log logger = LogFactory.getLog(User.class);
+
 	// ~ Instance fields
 	// ================================================================================================
 	private String password;
@@ -244,8 +251,99 @@ public class User implements UserDetails, CredentialsContainer {
 		return sb.toString();
 	}
 
+	/**
+	 * Creates a UserBuilder with a specified user name
+	 *
+	 * @param username the username to use
+	 * @return the UserBuilder
+	 */
 	public static UserBuilder withUsername(String username) {
-		return new UserBuilder().username(username);
+		return builder().username(username);
+	}
+
+	/**
+	 * Creates a UserBuilder
+	 *
+	 * @return the UserBuilder
+	 */
+	public static UserBuilder builder() {
+		return new UserBuilder();
+	}
+
+	/**
+	 * <p>
+	 * <b>WARNING:</b> This method is considered unsafe for production and is only intended
+	 * for sample applications.
+	 * </p>
+	 * <p>
+	 * Creates a user and automatically encodes the provided password using
+	 * {@code PasswordEncoderFactories.createDelegatingPasswordEncoder()}. For example:
+	 * </p>
+	 *
+	 * <pre>
+	 * <code>
+	 * UserDetails user = User.withDefaultPasswordEncoder()
+	 *     .username("user")
+	 *     .password("password")
+	 *     .roles("USER")
+	 *     .build();
+	 * // outputs {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
+	 * System.out.println(user.getPassword());
+	 * </code>
+	 * </pre>
+	 *
+	 * This is not safe for production (it is intended for getting started experience)
+	 * because the password "password" is compiled into the source code and then is
+	 * included in memory at the time of creation. This means there are still ways to
+	 * recover the plain text password making it unsafe. It does provide a slight
+	 * improvement to using plain text passwords since the UserDetails password is
+	 * securely hashed. This means if the UserDetails password is accidentally exposed,
+	 * the password is securely stored.
+	 *
+	 * In a production setting, it is recommended to hash the password ahead of time.
+	 * For example:
+	 *
+	 * <pre>
+	 * <code>
+	 * PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+	 * // outputs {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
+	 * // remember the password that is printed out and use in the next step
+	 * System.out.println(encoder.encode("password"));
+	 * </code>
+	 * </pre>
+	 *
+	 * <pre>
+	 * <code>
+	 * UserDetails user = User.withUsername("user")
+	 *     .password("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG")
+	 *     .roles("USER")
+	 *     .build();
+	 * </code>
+	 * </pre>
+	 *
+	 * @return a UserBuilder that automatically encodes the password with the default
+	 * PasswordEncoder
+	 * @deprecated Using this method is not considered safe for production, but is
+	 * acceptable for demos and getting started. For production purposes, ensure the
+	 * password is encoded externally. See the method Javadoc for additional details.
+	 * There are no plans to remove this support. It is deprecated to indicate
+	 * that this is considered insecure for production purposes.
+	 */
+	@Deprecated
+	public static UserBuilder withDefaultPasswordEncoder() {
+		logger.warn("User.withDefaultPasswordEncoder() is considered unsafe for production and is only intended for sample applications.");
+		PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+		return builder().passwordEncoder(encoder);
+	}
+
+	public static UserBuilder withUserDetails(UserDetails userDetails) {
+		return withUsername(userDetails.getUsername())
+			.password(userDetails.getPassword())
+			.accountExpired(!userDetails.isAccountNonExpired())
+			.accountLocked(!userDetails.isAccountNonLocked())
+			.authorities(userDetails.getAuthorities())
+			.credentialsExpired(!userDetails.isCredentialsNonExpired())
+			.disabled(!userDetails.isEnabled());
 	}
 
 	/**
@@ -260,6 +358,7 @@ public class User implements UserDetails, CredentialsContainer {
 		private boolean accountLocked;
 		private boolean credentialsExpired;
 		private boolean disabled;
+		private PasswordEncoder passwordEncoder = NoOpPasswordEncoder.getInstance();
 
 		/**
 		 * Creates a new instance
@@ -274,7 +373,7 @@ public class User implements UserDetails, CredentialsContainer {
 		 * @return the {@link UserBuilder} for method chaining (i.e. to populate
 		 * additional attributes for this user)
 		 */
-		private UserBuilder username(String username) {
+		public UserBuilder username(String username) {
 			Assert.notNull(username, "username cannot be null");
 			this.username = username;
 			return this;
@@ -293,6 +392,20 @@ public class User implements UserDetails, CredentialsContainer {
 			return this;
 		}
 
+		/**
+		 * Encodes the current password (if non-null) and any future passwords supplied
+		 * to {@link #password(String)}.
+		 *
+		 * @param encoder the encoder to use
+		 * @return the {@link UserBuilder} for method chaining (i.e. to populate
+		 * additional attributes for this user)
+		 */
+		private UserBuilder passwordEncoder(PasswordEncoder encoder) {
+			Assert.notNull(encoder, "encoder cannot be null");
+			this.passwordEncoder = encoder;
+			return this;
+		}
+
 		/**
 		 * Populates the roles. This method is a shortcut for calling
 		 * {@link #authorities(String...)}, but automatically prefixes each entry with
@@ -351,7 +464,7 @@ public class User implements UserDetails, CredentialsContainer {
 		 * additional attributes for this user)
 		 * @see #roles(String...)
 		 */
-		public UserBuilder authorities(List<? extends GrantedAuthority> authorities) {
+		public UserBuilder authorities(Collection<? extends GrantedAuthority> authorities) {
 			this.authorities = new ArrayList<GrantedAuthority>(authorities);
 			return this;
 		}
@@ -418,7 +531,8 @@ public class User implements UserDetails, CredentialsContainer {
 		}
 
 		public UserDetails build() {
-			return new User(username, password, !disabled, !accountExpired,
+			String encodedPassword = this.passwordEncoder.encode(password);
+			return new User(username, encodedPassword, !disabled, !accountExpired,
 					!credentialsExpired, !accountLocked, authorities);
 		}
 	}