docs
User provisioning provides comprehensive CRUD (Create, Read, Update, Delete) operations for managing user accounts and groups. This extends the basic UserDetailsService with full user lifecycle management capabilities.
Core Capabilities:
UserDetailsManager extends UserDetailsService with CRUD operationsGroupManager interface (groups with authorities)InMemoryUserDetailsManager (for testing/development)JdbcUserDetailsManager (persistent, with group support)Key Interfaces and Classes:
UserDetailsManager - Interface: createUser(), updateUser(), deleteUser(), changePassword(), userExists()GroupManager - Interface: createGroup(), deleteGroup(), addUserToGroup(), findGroupAuthorities(), etc.InMemoryUserDetailsManager - In-memory implementation (implements both interfaces)JdbcUserDetailsManager - JDBC implementation (implements both interfaces, extends JdbcDaoImpl)Default Behaviors:
createUser() throws exception if user already existsupdateUser() replaces entire user (must provide all fields)deleteUser() removes user and all associated authorities/groupschangePassword() validates old password before changing (requires AuthenticationManager)userExists() returns boolean (no exception if not found)Threading Model:
Lifecycle:
Exceptions:
createUser() if userExists() returns trueupdateUser(), deleteUser() if user doesn't existchangePassword() if old password incorrectEdge Cases:
userExists() before createUser()createUser() / updateUser()JdbcUserDetailsManager allows custom SQL via setter methodsExtended interface providing full user management capabilities beyond just loading user details.
public interface UserDetailsManager extends UserDetailsService{ .api }
Key Methods:
void createUser(UserDetails user){ .api }
Creates a new user account with the provided details.
void updateUser(UserDetails user){ .api }
Updates an existing user's information.
void deleteUser(String username){ .api }
Deletes a user account by username.
void changePassword(String oldPassword, String newPassword){ .api }
Changes the password for the currently authenticated user.
boolean userExists(String username){ .api }
Checks if a user with the given username exists.
Usage Example:
@Service
public class UserProvisioningService {
private final UserDetailsManager userDetailsManager;
public void registerNewUser(RegistrationRequest request) {
if (userDetailsManager.userExists(request.getUsername())) {
throw new UserAlreadyExistsException(request.getUsername());
}
UserDetails user = User.builder()
.username(request.getUsername())
.password(passwordEncoder.encode(request.getPassword()))
.authorities("ROLE_USER")
.build();
userDetailsManager.createUser(user);
}
public void updateUserProfile(String username, ProfileUpdate update) {
UserDetails existing = userDetailsManager.loadUserByUsername(username);
UserDetails updated = User.builder()
.username(existing.getUsername())
.password(existing.getPassword())
.authorities(existing.getAuthorities())
.accountExpired(!update.isAccountValid())
.accountLocked(update.isLocked())
.credentialsExpired(!update.areCredentialsValid())
.disabled(!update.isEnabled())
.build();
userDetailsManager.updateUser(updated);
}
public void removeUser(String username) {
if (!userDetailsManager.userExists(username)) {
throw new UsernameNotFoundException(username);
}
userDetailsManager.deleteUser(username);
}
}Interface for managing user groups and group-based authorities.
public interface GroupManager{ .api }
Key Methods:
List<String> findAllGroups(){ .api }
Returns a list of all group names.
List<String> findUsersInGroup(String groupName){ .api }
Returns usernames of all users in the specified group.
void createGroup(String groupName, List<GrantedAuthority> authorities){ .api }
Creates a new group with the specified authorities.
void deleteGroup(String groupName){ .api }
Deletes a group.
void renameGroup(String oldName, String newName){ .api }
Renames an existing group.
void addUserToGroup(String username, String groupName){ .api }
Adds a user to a group.
void removeUserFromGroup(String username, String groupName){ .api }
Removes a user from a group.
List<GrantedAuthority> findGroupAuthorities(String groupName){ .api }
Returns the authorities assigned to a group.
void addGroupAuthority(String groupName, GrantedAuthority authority){ .api }
Adds an authority to a group.
void removeGroupAuthority(String groupName, GrantedAuthority authority){ .api }
Removes an authority from a group.
Usage Example:
@Service
public class GroupManagementService {
private final GroupManager groupManager;
public void createDepartmentGroup(String department, Set<String> permissions) {
List<GrantedAuthority> authorities = permissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
groupManager.createGroup(department, authorities);
}
public void assignUserToDepartment(String username, String department) {
if (!groupManager.findAllGroups().contains(department)) {
throw new IllegalArgumentException("Group does not exist: " + department);
}
groupManager.addUserToGroup(username, department);
}
public void grantGroupPermission(String groupName, String permission) {
GrantedAuthority authority = new SimpleGrantedAuthority(permission);
groupManager.addGroupAuthority(groupName, authority);
}
public List<String> getDepartmentMembers(String department) {
return groupManager.findUsersInGroup(department);
}
}In-memory implementation of UserDetailsManager, useful for development and testing.
public class InMemoryUserDetailsManager
implements UserDetailsManager, UserDetailsPasswordService{ .api }
Constructors:
public InMemoryUserDetailsManager(){ .api }
Creates an empty manager.
public InMemoryUserDetailsManager(UserDetails... users){ .api }
Creates a manager with initial users.
public InMemoryUserDetailsManager(Collection<UserDetails> users){ .api }
Creates a manager with a collection of users.
Configuration Examples:
@Configuration
public class InMemoryUserConfig {
@Bean
public InMemoryUserDetailsManager userDetailsManager() {
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$...")
.roles("ADMIN", "USER")
.build();
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$...")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin, user);
}
}Dynamic User Management:
@Service
public class DynamicUserService {
private final InMemoryUserDetailsManager userDetailsManager;
public void registerUser(String username, String password) {
UserDetails user = User.builder()
.username(username)
.password("{noop}" + password) // For demo only - use proper encoding
.roles("USER")
.build();
userDetailsManager.createUser(user);
}
public void promoteToAdmin(String username) {
UserDetails user = userDetailsManager.loadUserByUsername(username);
Collection<GrantedAuthority> updatedAuthorities =
new ArrayList<>(user.getAuthorities());
updatedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
UserDetails updated = User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(updatedAuthorities)
.build();
userDetailsManager.updateUser(updated);
}
public boolean isUsernameTaken(String username) {
return userDetailsManager.userExists(username);
}
}Testing Setup:
@TestConfiguration
public class TestSecurityConfig {
@Bean
public InMemoryUserDetailsManager testUsers() {
UserDetails testAdmin = User.builder()
.username("testadmin")
.password("{noop}admin123")
.roles("ADMIN")
.build();
UserDetails testUser = User.builder()
.username("testuser")
.password("{noop}user123")
.roles("USER")
.build();
UserDetails testGuest = User.builder()
.username("testguest")
.password("{noop}guest123")
.roles("GUEST")
.disabled(false)
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.build();
return new InMemoryUserDetailsManager(testAdmin, testUser, testGuest);
}
}JDBC-based implementation supporting both UserDetailsManager and GroupManager.
public class JdbcUserDetailsManager
extends JdbcDaoImpl
implements UserDetailsManager, GroupManager{ .api }
Default SQL Schema:
-- Users table
CREATE TABLE users (
username VARCHAR(50) NOT NULL PRIMARY KEY,
password VARCHAR(500) NOT NULL,
enabled BOOLEAN NOT NULL
);
-- Authorities table
CREATE TABLE authorities (
username VARCHAR(50) NOT NULL,
authority VARCHAR(50) NOT NULL,
CONSTRAINT fk_authorities_users FOREIGN KEY(username) REFERENCES users(username)
);
CREATE UNIQUE INDEX ix_auth_username ON authorities (username, authority);
-- Groups table
CREATE TABLE groups (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
group_name VARCHAR(50) NOT NULL
);
-- Group authorities table
CREATE TABLE group_authorities (
group_id BIGINT NOT NULL,
authority VARCHAR(50) NOT NULL,
CONSTRAINT fk_group_authorities_group FOREIGN KEY(group_id) REFERENCES groups(id)
);
-- Group members table
CREATE TABLE group_members (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
username VARCHAR(50) NOT NULL,
group_id BIGINT NOT NULL,
CONSTRAINT fk_group_members_group FOREIGN KEY(group_id) REFERENCES groups(id)
);Default SQL Queries:
// User Operations
CREATE_USER_SQL = "INSERT INTO users (username, password, enabled) VALUES (?, ?, ?)";
UPDATE_USER_SQL = "UPDATE users SET password = ?, enabled = ? WHERE username = ?";
DELETE_USER_SQL = "DELETE FROM users WHERE username = ?";
USER_EXISTS_SQL = "SELECT username FROM users WHERE username = ?";
CHANGE_PASSWORD_SQL = "UPDATE users SET password = ? WHERE username = ?";
// Authority Operations
INSERT_AUTHORITY_SQL = "INSERT INTO authorities (username, authority) VALUES (?, ?)";
DELETE_USER_AUTHORITIES_SQL = "DELETE FROM authorities WHERE username = ?";
// Group Operations
INSERT_GROUP_SQL = "INSERT INTO groups (group_name) VALUES (?)";
FIND_GROUPS_SQL = "SELECT group_name FROM groups";
FIND_USERS_IN_GROUP_SQL = "SELECT username FROM group_members gm, groups g " +
"WHERE gm.group_id = g.id AND g.group_name = ?";{ .api }
Configuration:
@Configuration
public class JdbcUserManagementConfig {
@Bean
public JdbcUserDetailsManager jdbcUserDetailsManager(DataSource dataSource) {
JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
// Optional: Customize SQL queries
manager.setCreateUserSql(
"INSERT INTO users (username, password, enabled) VALUES (?, ?, ?)");
manager.setUpdateUserSql(
"UPDATE users SET password = ?, enabled = ? WHERE username = ?");
manager.setDeleteUserSql(
"DELETE FROM users WHERE username = ?");
manager.setUserExistsSql(
"SELECT username FROM users WHERE username = ?");
manager.setChangePasswordSql(
"UPDATE users SET password = ? WHERE username = ?");
return manager;
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:data.sql")
.build();
}
}Key Methods:
void setCreateUserSql(String createUserSql){ .api }
Customizes the SQL for creating users.
void setUpdateUserSql(String updateUserSql){ .api }
Customizes the SQL for updating users.
void setDeleteUserSql(String deleteSql){ .api }
Customizes the SQL for deleting users.
void setUserExistsSql(String userExistsSql){ .api }
Customizes the SQL for checking user existence.
void setChangePasswordSql(String changePasswordSql){ .api }
Customizes the SQL for changing passwords.
Complete Service Example:
@Service
@Transactional
public class JdbcUserProvisioningService {
private final JdbcUserDetailsManager userManager;
private final PasswordEncoder passwordEncoder;
public void createUserAccount(UserRegistration registration) {
if (userManager.userExists(registration.getUsername())) {
throw new DuplicateUsernameException(registration.getUsername());
}
UserDetails user = User.builder()
.username(registration.getUsername())
.password(passwordEncoder.encode(registration.getPassword()))
.authorities(registration.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.toList())
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build();
userManager.createUser(user);
}
public void setupUserGroups(String username, List<String> groupNames) {
for (String groupName : groupNames) {
userManager.addUserToGroup(username, groupName);
}
}
public void createOrganizationalGroup(String groupName, List<String> permissions) {
List<GrantedAuthority> authorities = permissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
userManager.createGroup(groupName, authorities);
}
public void disableUser(String username) {
UserDetails user = userManager.loadUserByUsername(username);
UserDetails disabled = User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(user.getAuthorities())
.disabled(true)
.build();
userManager.updateUser(disabled);
}
public void changeUserPassword(String username, String newPassword) {
UserDetails user = userManager.loadUserByUsername(username);
UserDetails updated = User.builder()
.username(user.getUsername())
.password(passwordEncoder.encode(newPassword))
.authorities(user.getAuthorities())
.build();
userManager.updateUser(updated);
}
public List<String> getUserGroups(String username) {
return userManager.findAllGroups().stream()
.filter(group -> userManager.findUsersInGroup(group).contains(username))
.collect(Collectors.toList());
}
}@Service
public class GroupAdministrationService {
private final GroupManager groupManager;
public void createDepartment(String departmentName, Set<String> basePermissions) {
List<GrantedAuthority> authorities = basePermissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
groupManager.createGroup(departmentName, authorities);
}
public void assignUserToDepartment(String username, String department) {
groupManager.addUserToGroup(username, department);
}
public void transferUser(String username, String fromDept, String toDept) {
groupManager.removeUserFromGroup(username, fromDept);
groupManager.addUserToGroup(username, toDept);
}
public void grantDepartmentPermission(String department, String permission) {
GrantedAuthority authority = new SimpleGrantedAuthority(permission);
groupManager.addGroupAuthority(department, authority);
}
public void revokeDepartmentPermission(String department, String permission) {
GrantedAuthority authority = new SimpleGrantedAuthority(permission);
groupManager.removeGroupAuthority(department, authority);
}
public void renameDepartment(String oldName, String newName) {
groupManager.renameGroup(oldName, newName);
}
public void deleteDepartment(String department) {
// First, remove all users from the group
List<String> members = groupManager.findUsersInGroup(department);
members.forEach(username ->
groupManager.removeUserFromGroup(username, department));
// Then delete the group
groupManager.deleteGroup(department);
}
public Map<String, List<String>> getDepartmentStructure() {
Map<String, List<String>> structure = new HashMap<>();
for (String group : groupManager.findAllGroups()) {
List<String> members = groupManager.findUsersInGroup(group);
structure.put(group, members);
}
return structure;
}
public Set<String> getUserPermissions(String username) {
Set<String> permissions = new HashSet<>();
for (String group : groupManager.findAllGroups()) {
if (groupManager.findUsersInGroup(group).contains(username)) {
List<GrantedAuthority> authorities =
groupManager.findGroupAuthorities(group);
authorities.stream()
.map(GrantedAuthority::getAuthority)
.forEach(permissions::add);
}
}
return permissions;
}
}@RestController
@RequestMapping("/api/admin/users")
public class UserAdministrationController {
private final UserDetailsManager userManager;
private final GroupManager groupManager;
private final PasswordEncoder passwordEncoder;
@PostMapping
public ResponseEntity<Void> createUser(@RequestBody CreateUserRequest request) {
UserDetails user = User.builder()
.username(request.getUsername())
.password(passwordEncoder.encode(request.getPassword()))
.authorities(request.getRoles())
.build();
userManager.createUser(user);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@PutMapping("/{username}")
public ResponseEntity<Void> updateUser(
@PathVariable String username,
@RequestBody UpdateUserRequest request
) {
UserDetails existing = userManager.loadUserByUsername(username);
UserDetails updated = User.builder()
.username(username)
.password(existing.getPassword())
.authorities(request.getRoles())
.disabled(!request.isEnabled())
.accountLocked(request.isLocked())
.build();
userManager.updateUser(updated);
return ResponseEntity.ok().build();
}
@DeleteMapping("/{username}")
public ResponseEntity<Void> deleteUser(@PathVariable String username) {
userManager.deleteUser(username);
return ResponseEntity.noContent().build();
}
@GetMapping("/{username}/exists")
public ResponseEntity<Boolean> userExists(@PathVariable String username) {
return ResponseEntity.ok(userManager.userExists(username));
}
@PostMapping("/groups")
public ResponseEntity<Void> createGroup(@RequestBody CreateGroupRequest request) {
List<GrantedAuthority> authorities = request.getPermissions().stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
groupManager.createGroup(request.getName(), authorities);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@PostMapping("/groups/{groupName}/members/{username}")
public ResponseEntity<Void> addUserToGroup(
@PathVariable String groupName,
@PathVariable String username
) {
groupManager.addUserToGroup(username, groupName);
return ResponseEntity.ok().build();
}
@GetMapping("/groups/{groupName}/members")
public ResponseEntity<List<String>> getGroupMembers(@PathVariable String groupName) {
return ResponseEntity.ok(groupManager.findUsersInGroup(groupName));
}
}org.springframework.security.provisioning