Spring Boot Advanced: Security and Production
Spring Security Basics
In this lesson you will
- Explain what Spring Security does by default
- Configure which requests need authentication and which roles
- Store passwords with a proper encoder and test secured endpoints
Introduction
Spring Security is the standard framework for protecting Spring applications. It handles authentication (who are you?) and authorisation (what may you do?), and defends against common web attacks. Security is easy to get wrong, so use the framework's defaults, keep dependencies up to date and have important systems reviewed. This lesson is written for Spring Boot 4.1.2 with Spring Security 7.1. I checked that the class names used exist in those releases, but the code was not compiled or run here.
Add the dependency Spring Security (spring-boot-starter-security) when generating a project.
What happens by default
Just by adding the starter:
- Every endpoint requires authentication.
- A default user named
useris created, with a random password printed in the application log at start-up. - Form login and HTTP Basic are available, and CSRF protection is enabled for state-changing requests.
- Security headers are added.
That is a safe starting point, but not a real user system.
Configuring the filter chain
Define a SecurityFilterChain bean that says which requests are allowed.
Java
package com.example.demo.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());
return http.build();
}
}- Rules are checked in order, and the first match wins, so put specific rules before general ones.
permitAll()allows anyone,authenticated()requires login andhasRole("ADMIN")requires that role.anyRequest().authenticated()at the end means deny by default: anything you forgot still needs a login. This is the safe habit.
Users and passwords
For learning, define users in memory. Real applications load users from a database.
Java
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
UserDetailsService users(PasswordEncoder encoder) {
var ada = User.withUsername("ada")
.password(encoder.encode("change-me-please"))
.roles("USER")
.build();
var admin = User.withUsername("root")
.password(encoder.encode("change-me-too"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(ada, admin);
}The point is the encoder: passwords are stored as slow, salted hashes (BCrypt here), never as plain text or with a fast hash. Do not put real passwords in source code. These example passwords are placeholders.
Quick quiz
Why end the authorization rules with anyRequest().authenticated()?
Method-level security
You can also protect individual methods:
Java
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@EnableMethodSecurity // on a configuration class
@PreAuthorize("hasRole('ADMIN')")
public void deleteBook(long id) { /* ... */ }This is useful for rules that depend on who owns a record, such as @PreAuthorize("#username == authentication.name"). Checking ownership is essential: being logged in does not mean you may read someone else's data.
CSRF and APIs
CSRF attacks trick a logged-in browser into sending unwanted requests. Spring protects against this by default. For a stateless API that uses tokens in an Authorization header and no cookies for authentication, CSRF protection is often disabled deliberately. Do that only when you understand why, and not simply to make a failing request pass.
Beyond Basic auth
HTTP Basic sends the username and password on every request, so it requires HTTPS and is mostly for learning and service-to-service use. Production applications commonly use:
- OAuth 2.0 / OpenID Connect with an identity provider, using Spring Security's resource server support to validate JWT access tokens
- Session-based login for server-rendered apps
- Multi-factor authentication and account recovery through the identity provider
Using an established identity provider avoids building password storage and recovery yourself.
Testing secured endpoints
With spring-security-test on the classpath (the Initializr adds spring-boot-starter-security-test):
Java
import org.springframework.security.test.context.support.WithMockUser;
@Test
@WithMockUser(roles = "ADMIN")
void adminCanDelete() throws Exception {
mockMvc.perform(delete("/admin/books/1").with(
org.springframework.security.test.web.servlet.request
.SecurityMockMvcRequestPostProcessors.csrf()))
.andExpect(status().isNoContent());
}
@Test
void anonymousIsRejected() throws Exception {
mockMvc.perform(get("/books")).andExpect(status().isUnauthorized());
}Test both allowed and forbidden cases. Many security bugs are missing checks.
Security checklist
- HTTPS everywhere in production
- Passwords hashed with BCrypt or similar, no plain text
- Deny by default, least privilege
- Ownership checks on data
- Secrets from the environment, never in the repository
- Dependencies updated, and security advisories monitored
- Actuator and H2 console not exposed publicly
- Rate limiting and lockouts against password guessing (often at a gateway)
- Logs free of passwords and tokens
Try it yourself
Secure your book API: everyone with a login can read, only an admin can delete. Write one test for each case, including an anonymous request, and remember to keep sample credentials out of version control.
Summary
- Adding Spring Security secures every endpoint by default, and a SecurityFilterChain bean defines the rules.
- Store passwords with BCrypt, apply deny-by-default and check ownership as well as roles.
- Use HTTPS and, for real systems, a proper identity provider with OAuth 2 and tokens.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading