|
@@ -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);
|
|
|
}
|
|
|
}
|