
Spring Security is a powerful framework that handles authentication and authorization in Spring applications.
The heart of Spring Security setup is the security configuration class (WebSecurityConfig). This is where we define rules about who can access what in your application:
@Configuration
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// Public endpoints anyone can access
.requestMatchers("/public/**", "/api/login").permitAll()
// Only admins can access these endpoints
.requestMatchers("/admin/**").hasRole("ADMIN")
// All other endpoints need authentication
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
);
return http.build();
}
}
Think of this configuration as a security checkpoint. Just like how a building might have different access levels for different areas, this configuration defines which parts of our application require what level of security clearance.
The User class is our digital identity card in the application. It implements UserDetails to provide Spring Security with essential information about the user:
@Entity
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private List<GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities; // Returns user's roles and permissions
}
// Additional UserDetails methods
@Override
public boolean isAccountNonExpired() {
return true;
}
}
The UserDetailsService acts like a security guard checking IDs. It loads user information and verifies credentials:
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) {
return userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
}
Never store passwords in plain text!!! Spring Security provides password encoders to securely hash passwords:
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Service
public class UserService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
public void createUser(UserRegistrationDTO dto) {
User user = new User();
user.setUsername(dto.getUsername());
// Encode password before saving
user.setPassword(passwordEncoder.encode(dto.getPassword()));
userRepository.save(user);
}
}
When your frontend and backend are on different domains (like in modern single-page applications), we need to configure CORS!!!
@Configuration
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList(
"http://localhost:3000"
));
config.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE"
));
config.setAllowedHeaders(Arrays.asList("*"));
return config;
}))
.csrf(csrf -> csrf.disable()); // Often disabled for REST APIs
return http.build();
}
}
Here's how the authentication process works:
UserDetailsService loads the user's informationPassword Security: Always use password encoding - never store plain text passwords.
user.setPassword(passwordEncoder.encode(rawPassword));
Role-Based Access: Use specific roles for different access levels:
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasRole("USER")
Error Handling: Provide secure but helpful error messages:
try {
// Authentication logic
} catch (AuthenticationException e) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body("Invalid credentials");
}
Session Management: Configure session management for our needs:
http.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1)
);