Spring Security Users
Introduction
Managing users securely is a fundamental aspect of any application. Spring Security provides a robust framework for handling user authentication and authorization. In this guide, we'll explore how to configure and manage users in Spring Security, from basic in-memory authentication to more sophisticated database-backed user stores.
User management in Spring Security revolves around two core concepts:
- Authentication: Verifying that users are who they claim to be
- Authorization: Determining what authenticated users are allowed to do
By the end of this tutorial, you'll understand how to implement different user management strategies in Spring Security and choose the right approach for your applications.
Basic User Configuration
In-Memory Authentication
The simplest way to configure users in Spring Security is with in-memory authentication. This approach is excellent for learning, testing, or prototyping but not recommended for production environments.
Let's set up a basic configuration with in-memory users:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG") // "password"
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG") // "password"
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
In this example:
- We define two users: a regular user and an admin
- Passwords are encoded with BCrypt (recommended)
- Users are assigned roles that determine their access rights
- The configuration uses the new Spring Security 6.x style with lambda expressions
Always use password encoding even for development environments. Spring Security 5+ requires encoded passwords by default.
How It Works
When a user attempts to access a protected resource:
- Spring Security intercepts the request
- The user is redirected to a login form
- After submitting credentials, Spring Security checks against the configured UserDetailsService
- If authenticated, Spring Security grants or denies access based on the user's roles
User Store Options
JDBC Authentication
For a more robust solution, you can store users in a database accessed via JDBC:
@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
// Create tables if they don't exist
if (!users.userExists("user")) {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();
users.createUser(user);
}
if (!users.userExists("admin")) {
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("password"))
.roles("ADMIN", "USER")
.build();
users.createUser(admin);
}
return users;
}
The JDBC approach uses Spring Security's default schema with the following tables:
users
- Stores user credentialsauthorities
- Stores user roles/authorities
You can create these tables with the following SQL (for most databases):
create table users(
username varchar(50) not null primary key,
password varchar(500) not null,
enabled boolean not null
);
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);
Custom UserDetailsService
For complete control over user management, implement your own UserDetailsService:
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.isEnabled(),
true, // accountNonExpired
true, // credentialsNonExpired
true, // accountNonLocked
getAuthorities(user.getRoles())
);
}
private Collection<? extends GrantedAuthority> getAuthorities(Set<Role> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
.collect(Collectors.toList());
}
}
This approach requires:
- A User entity:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
private String password;
private boolean enabled;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
// getters and setters
}
- A Role entity:
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String name;
// getters and setters
}
- A UserRepository:
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
User Registration and Management
Let's implement a simple user registration flow:
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
public UserController(UserService userService, PasswordEncoder passwordEncoder) {
this.userService = userService;
this.passwordEncoder = passwordEncoder;
}
@PostMapping("/register")
public ResponseEntity<?> registerUser(@RequestBody RegistrationRequest request) {
// Check if username already exists
if (userService.existsByUsername(request.getUsername())) {
return ResponseEntity
.badRequest()
.body("Username already taken");
}
// Create new user with encoded password
User user = new User();
user.setUsername(request.getUsername());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setEnabled(true);
// Assign default role
Role userRole = userService.findRoleByName("USER")
.orElseThrow(() -> new RuntimeException("Role not found"));
user.getRoles().add(userRole);
userService.saveUser(user);
return ResponseEntity.ok("User registered successfully");
}
}
The RegistrationRequest
class:
public class RegistrationRequest {
private String username;
private String password;
// getters and setters
}
Password Management
Spring Security provides several password encoders for secure password storage. BCrypt is recommended for most applications:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
To update a user's password:
@PostMapping("/change-password")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> changePassword(@RequestBody PasswordChangeRequest request,
Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String username = userDetails.getUsername();
// Check if current password matches
if (!userService.checkPassword(username, request.getCurrentPassword())) {
return ResponseEntity
.badRequest()
.body("Current password is incorrect");
}
// Update password
userService.updatePassword(username, passwordEncoder.encode(request.getNewPassword()));
return ResponseEntity.ok("Password updated successfully");
}
Real-World Example: Complete User System
Let's put everything together with a more complete example of a user management system:
- Security Configuration:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
private final CustomUserDetailsService userDetailsService;
public SecurityConfig(CustomUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // For API use, typically disabled
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/api/public/**", "/api/auth/**", "/api/users/register").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider())
.addFilterBefore(new JwtAuthFilter(), UsernamePasswordAuthenticationFilter.class)
.formLogin(login -> login.disable())
.httpBasic(basic -> basic.disable());
return http.build();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- Authentication Controller:
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
// Constructor injection
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
} catch (BadCredentialsException e) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body("Invalid username or password");
}
}
}
This example introduces JWT (JSON Web Token) authentication, which is common for modern web applications, especially those with separate frontend applications.
Best Practices for User Management
-
Always Encode Passwords: Never store plaintext passwords. Use strong encoders like BCrypt.
-
Role-Based Access Control: Structure your permissions around roles, not individual users.
-
Principle of Least Privilege: Give users only the permissions they need.
-
Account Lockout: Implement account lockout after multiple failed login attempts.
-
Password Reset: Implement a secure password reset process that doesn't expose user data.
-
Multi-Factor Authentication: For sensitive applications, use MFA for an extra layer of security.
-
Audit Logging: Log authentication attempts, password changes, and privilege changes.
Example of audit logging:
@Component
public class SecurityAuditLogger {
private static final Logger logger = LoggerFactory.getLogger(SecurityAuditLogger.class);
@EventListener
public void auditAuthenticationSuccess(AuthenticationSuccessEvent event) {
logger.info("Authentication success: {}", event.getAuthentication().getName());
}
@EventListener
public void auditAuthenticationFailure(AbstractAuthenticationFailureEvent event) {
logger.warn("Authentication failure for user {}: {}",
event.getAuthentication().getName(),
event.getException().getMessage());
}
}
Summary
In this guide, you've learned how to:
- Configure users in Spring Security using in-memory, JDBC, and custom user details services
- Implement proper password encoding and management
- Create a user registration system
- Apply best practices for user management
- Set up role-based access control
- Add auditing for security events
Understanding how to properly manage users in Spring Security is critical for building secure applications. By following the patterns and best practices in this guide, you can create robust authentication and authorization systems that protect your application and its users.
Additional Resources
- Spring Security Reference Documentation
- OWASP Password Storage Cheat Sheet
- OAuth 2.0 and OpenID Connect with Spring Security
Exercises
- Implement a complete user registration system with email verification.
- Add "Forgot Password" functionality using a secure token-based approach.
- Implement remember-me functionality for persistent logins.
- Create a user management dashboard for administrators to create, update, and delete users.
- Add multi-factor authentication using email or SMS codes.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)