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 eb8cb53d85..69a645c6e4 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 @@ -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(); + } + + /** + *
+ * WARNING: This method is considered unsafe for production and is only intended + * for sample applications. + *
+ *+ * Creates a user and automatically encodes the provided password using + * {@code PasswordEncoderFactories.createDelegatingPasswordEncoder()}. For example: + *
+ * + *
+ *
+ * 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());
+ *
+ *
+ *
+ * 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:
+ *
+ *
+ *
+ * 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"));
+ *
+ *
+ *
+ *
+ *
+ * UserDetails user = User.withUsername("user")
+ * .password("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG")
+ * .roles("USER")
+ * .build();
+ *
+ *
+ *
+ * @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