What is Spring Security?
Think of Spring Security as a building's security system:
- Authentication: ID check at entrance (Who are you?)
- Authorization: Access badges for different areas (What can you access?)
- Filters: Security guards checking each person
Spring Security is a powerful, highly customizable authentication and access-control framework for Java applications.
Setup
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Just adding this dependency enables:
- All endpoints require authentication
- Default login form at /login
- Default user: "user", password in console logs
Basic Configuration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// Authorization rules
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**", "/login", "/register").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
// Form login
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.permitAll()
)
// Logout
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
User Details Service
In-Memory Users (for testing)
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
Database Users
// Entity
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
private boolean enabled = true;
@ManyToMany(fetch = FetchType.EAGER)
private Set<Role> roles = new HashSet<>();
}
// Custom UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(user.getRoles().stream()
.map(r -> new SimpleGrantedAuthority("ROLE_" + r.getName()))
.toList())
.disabled(!user.isEnabled())
.build();
}
}
JWT Authentication
// JWT Configuration for REST APIs
@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// Disable CSRF for stateless APIs
.csrf(csrf -> csrf.disable())
// Stateless session
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// Authorization
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
// Add JWT filter before UsernamePasswordAuthenticationFilter
.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
// JWT Filter
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws Exception {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String jwt = authHeader.substring(7);
String username = jwtService.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource()
.buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
Method Security
@Configuration
@EnableMethodSecurity // Enable method-level security
public class MethodSecurityConfig {
}
@Service
public class UserService {
// Role-based access
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { }
// Multiple roles
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public void updateUser(User user) { }
// Permission-based
@PreAuthorize("hasAuthority('user:write')")
public void createUser(User user) { }
// Access method parameters
@PreAuthorize("#userId == authentication.principal.id")
public User getProfile(Long userId) { }
// Check return value
@PostAuthorize("returnObject.owner == authentication.name")
public Document getDocument(Long id) { }
// Filter collections
@PostFilter("filterObject.owner == authentication.name")
public List<Document> getAllDocuments() { }
}
Accessing Current User
@RestController
public class ProfileController {
// Method 1: Principal parameter
@GetMapping("/profile1")
public String getProfile(Principal principal) {
return "Hello, " + principal.getName();
}
// Method 2: @AuthenticationPrincipal
@GetMapping("/profile2")
public String getProfile(@AuthenticationPrincipal UserDetails user) {
return "Hello, " + user.getUsername();
}
// Method 3: SecurityContextHolder
@GetMapping("/profile3")
public String getProfile() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return "Hello, " + auth.getName();
}
// Method 4: Custom user object
@GetMapping("/profile4")
public String getProfile(@AuthenticationPrincipal CustomUser user) {
return "Hello, " + user.getFullName();
}
}
CORS Configuration
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// ... other config
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
Summary
- Spring Security: Authentication and authorization framework
- SecurityFilterChain: Configures security rules
- UserDetailsService: Loads user data
- PasswordEncoder: Hashes passwords (BCrypt)
- @PreAuthorize: Method-level security
- JWT: Stateless token-based authentication
- CORS: Cross-origin resource sharing