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
This commit is contained in:
Rob Winch 2016-10-21 16:42:03 -05:00
parent 50b72dddbc
commit f432c04111
5 changed files with 230 additions and 59 deletions

View File

@ -16,19 +16,16 @@
package org.springframework.security.config.annotation.authentication.configurers.provisioning; package org.springframework.security.config.annotation.authentication.configurers.provisioning;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.springframework.security.config.annotation.SecurityBuilder; import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder; import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsServiceConfigurer; import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsServiceConfigurer;
import org.springframework.security.core.GrantedAuthority; 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;
import org.springframework.security.core.userdetails.User.UserBuilder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.UserDetailsManager; import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.util.Assert;
/** /**
* Base class for populating an * 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. * should provided. The remaining attributes have reasonable defaults.
*/ */
public class UserDetailsBuilder { public class UserDetailsBuilder {
private String username; private UserBuilder user;
private String password;
private List<GrantedAuthority> authorities;
private boolean accountExpired;
private boolean accountLocked;
private boolean credentialsExpired;
private boolean disabled;
private final C builder; private final C builder;
/** /**
@ -117,8 +108,7 @@ public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C
* additional attributes for this user) * additional attributes for this user)
*/ */
private UserDetailsBuilder username(String username) { private UserDetailsBuilder username(String username) {
Assert.notNull(username, "username cannot be null"); this.user = User.withUsername(username);
this.username = username;
return this; return this;
} }
@ -130,8 +120,7 @@ public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C
* additional attributes for this user) * additional attributes for this user)
*/ */
public UserDetailsBuilder password(String password) { public UserDetailsBuilder password(String password) {
Assert.notNull(password, "password cannot be null"); this.user.password(password);
this.password = password;
return this; return this;
} }
@ -161,14 +150,8 @@ public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C
* additional attributes for this user) * additional attributes for this user)
*/ */
public UserDetailsBuilder roles(String... roles) { public UserDetailsBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>( this.user.roles(roles);
roles.length); return this;
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);
} }
/** /**
@ -181,7 +164,8 @@ public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C
* @see #roles(String...) * @see #roles(String...)
*/ */
public UserDetailsBuilder authorities(GrantedAuthority... authorities) { 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...) * @see #roles(String...)
*/ */
public UserDetailsBuilder authorities(List<? extends GrantedAuthority> authorities) { public UserDetailsBuilder authorities(List<? extends GrantedAuthority> authorities) {
this.authorities = new ArrayList<GrantedAuthority>(authorities); this.user.authorities(authorities);
return this; return this;
} }
@ -208,7 +192,8 @@ public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C
* @see #roles(String...) * @see #roles(String...)
*/ */
public UserDetailsBuilder authorities(String... authorities) { 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) * additional attributes for this user)
*/ */
public UserDetailsBuilder accountExpired(boolean accountExpired) { public UserDetailsBuilder accountExpired(boolean accountExpired) {
this.accountExpired = accountExpired; this.user.accountExpired(accountExpired);
return this; return this;
} }
@ -231,7 +216,7 @@ public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C
* additional attributes for this user) * additional attributes for this user)
*/ */
public UserDetailsBuilder accountLocked(boolean accountLocked) { public UserDetailsBuilder accountLocked(boolean accountLocked) {
this.accountLocked = accountLocked; this.user.accountLocked(accountLocked);
return this; return this;
} }
@ -243,7 +228,7 @@ public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C
* additional attributes for this user) * additional attributes for this user)
*/ */
public UserDetailsBuilder credentialsExpired(boolean credentialsExpired) { public UserDetailsBuilder credentialsExpired(boolean credentialsExpired) {
this.credentialsExpired = credentialsExpired; this.user.credentialsExpired(credentialsExpired);
return this; return this;
} }
@ -255,13 +240,12 @@ public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C
* additional attributes for this user) * additional attributes for this user)
*/ */
public UserDetailsBuilder disabled(boolean disabled) { public UserDetailsBuilder disabled(boolean disabled) {
this.disabled = disabled; this.user.disabled(disabled);
return this; return this;
} }
private UserDetails build() { private UserDetails build() {
return new User(username, password, !disabled, !accountExpired, return this.user.build();
!credentialsExpired, !accountLocked, authorities);
} }
} }
} }

View File

@ -17,9 +17,12 @@
package org.springframework.security.core.userdetails; package org.springframework.security.core.userdetails;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
@ -27,6 +30,8 @@ import java.util.TreeSet;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.CredentialsContainer; import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.SpringSecurityCoreVersion; 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; import org.springframework.util.Assert;
/** /**
@ -238,4 +243,183 @@ public class User implements UserDetails, CredentialsContainer {
return sb.toString(); 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);
}
}
} }

View File

@ -52,6 +52,9 @@ public class InMemoryUserDetailsManager implements UserDetailsManager {
private AuthenticationManager authenticationManager; private AuthenticationManager authenticationManager;
public InMemoryUserDetailsManager() {
}
public InMemoryUserDetailsManager(Collection<UserDetails> users) { public InMemoryUserDetailsManager(Collection<UserDetails> users) {
for (UserDetails user : users) { for (UserDetails user : users) {
createUser(user); createUser(user);

View File

@ -459,17 +459,15 @@ import org.springframework.security.config.annotation.web.configuration.*;
@EnableWebSecurity @EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired @Bean
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { public UserDetailsService userDetailsService() throws Exception {
auth InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
.inMemoryAuthentication() manager.createUser(User.withUsername("user").password("password").roles("USER").build());
.withUser("user").password("password").roles("USER"); 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: 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 * 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] [source,java]
---- ----
@Autowired @Bean
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { public UserDetailsService userDetailsService() throws Exception {
auth InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
.inMemoryAuthentication() manager.createUser(User.withUsername("user").password("password").roles("USER").build());
.withUser("user").password("password").roles("USER").and() manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build());
.withUser("admin").password("password").roles("USER", "ADMIN"); return manager;
} }
---- ----
@ -947,12 +945,12 @@ We can configure multiple HttpSecurity instances just as we can have multiple `<
---- ----
@EnableWebSecurity @EnableWebSecurity
public class MultiHttpSecurityConfig { public class MultiHttpSecurityConfig {
@Autowired @Bean
public void configureGlobal(AuthenticationManagerBuilder auth) { <1> public UserDetailsService userDetailsService() throws Exception {
auth InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
.inMemoryAuthentication() manager.createUser(User.withUsername("user").password("password").roles("USER").build());
.withUser("user").password("password").roles("USER").and() manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build());
.withUser("admin").password("password").roles("USER", "ADMIN"); return manager;
} }
@Configuration @Configuration

View File

@ -15,19 +15,21 @@
*/ */
package org.springframework.security.samples.config; package org.springframework.security.samples.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.*; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.*; import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@EnableWebSecurity @EnableWebSecurity
public class SecurityConfig { public class SecurityConfig {
// @formatter:off // @formatter:off
@Autowired @Bean
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { public UserDetailsService userDetailsService() throws Exception {
auth InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
.inMemoryAuthentication() manager.createUser(User.withUsername("user").password("password").roles("USER").build());
.withUser("user").password("password").roles("USER"); return manager;
} }
// @formatter:on // @formatter:on
} }