Why this matters
- Adding
spring-boot-starter-securitysecures every endpoint by default — understanding the filter chain prevents accidental lockdown or exposure. - Authentication (who are you?) and authorization (what can you do?) are separate concerns with distinct configuration points.
- Security is non-negotiable for production BookStore deployments handling user data and orders.
Add Spring Security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Every endpoint now requires authentication. A default user user with a random password appears in the startup log.
SecurityFilterChain configuration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/health", "/api/books/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/orders/**").authenticated()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Authorization rules for BookStore
permitAll()— No authentication required. Health checks, public catalog browsing.authenticated()— Any logged-in user. Order placement, profile management.hasRole("ADMIN")— Specific role required. Inventory management, user administration.hasAuthority("SCOPE_read")— Fine-grained permission (OAuth2 scopes).
UserDetailsService
@Service
public class BookStoreUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException(username));
return org.springframework.security.core.userdetails.User
.withUsername(user.getEmail())
.password(user.getPasswordHash())
.roles(user.getRole())
.build();
}
}
Spring calls loadUserByUsername during login to fetch credentials and roles.
Method-level security
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {}
@Service
public class BookService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteBook(Long id) {
repository.deleteById(id);
}
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public void updateProfile(Long id, UpdateProfileRequest request) { ... }
}
@PreAuthorize evaluates SpEL expressions before method execution — defense in depth beyond URL-level rules.
Password encoding
Never store plain-text passwords:
@Service
public class RegistrationService {
private final PasswordEncoder encoder;
private final UserRepository userRepository;
public User register(RegisterRequest request) {
User user = new User(
request.email(),
encoder.encode(request.password()),
"USER"
);
return userRepository.save(user);
}
}
BCryptPasswordEncoder hashes with a random salt per password — same input produces different hashes.
Testing secured endpoints
@WebMvcTest(BookController.class)
@Import(SecurityConfig.class)
class BookControllerSecurityTest {
@Autowired MockMvc mockMvc;
@Test
@WithMockUser(roles = "ADMIN")
void adminCanDelete() throws Exception {
mockMvc.perform(delete("/api/books/1"))
.andExpect(status().isNoContent());
}
@Test
void anonymousCannotDelete() throws Exception {
mockMvc.perform(delete("/api/books/1"))
.andExpect(status().isUnauthorized());
}
}
@WithMockUser simulates an authenticated user in tests.
Security context
After authentication, the SecurityContext holds the current user:
@Service
public class OrderService {
public Order placeOrder(PlaceOrderRequest request) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String userEmail = auth.getName();
// create order for authenticated user
}
}
Access it anywhere in the request thread — no need to pass the user through every method parameter.
Quick recall
Everything you need if you only revisit this box.
spring-boot-starter-securitysecures all endpoints by default.SecurityFilterChainconfigures authentication and authorization rules per URL pattern.UserDetailsServiceloads user credentials and roles from your data store.@PreAuthorizeenforces method-level access control with SpEL expressions.BCryptPasswordEncoderhashes passwords with per-password salts.SecurityContextHolderprovides the current authenticated user anywhere in the request thread.
Test yourself
Answer these before moving on — recall is what makes it stick.