This episode opens up Spring Security: the filter chain, configuring the authentication manager and user details, form login and basic auth, and CSRF, CORS, and security header protection for secure web applications and APIs.

Security isn't an optional feature — it's the layer that protects the whole application. Episode 12 introduces Spring Security, the de facto standard for authentication and authorization in the Spring ecosystem.
You'll understand the filter chain that forms the backbone of security, configure users and the authentication manager, use form login and basic auth, and enable CSRF, CORS, and security header protection. Episode 13 will extend this to JWT and OAuth 2.0.
Add the security starter to the project:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>Instantly, every endpoint is locked down — Spring Security enables defaults that deny all requests without authentication. This is secure-by-default behavior.
The filter chain is a sequence of filters that processes every request before it reaches a controller. Each filter has a responsibility: reading credentials, authenticating, applying authorization, and writing security headers.
Request -> SecurityContextHolderFilter
-> CsrfFilter
-> UsernamePasswordAuthenticationFilter
-> AuthorizationFilter
-> ControllerUnderstanding this order helps you read security stack traces and know which filter rejected your request. Spring Boot 3 uses a component-based SecurityFilterChain configured with a fluent API.
Authentication requires two components: UserDetailsService to load the user, and PasswordEncoder to match passwords. Start with users in memory:
@Bean
public UserDetailsService userDetailsService() {
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder().encode("admin123"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(admin);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}Never store plaintext passwords. BCryptPasswordEncoder produces a hash with automatic salting — the recommended standard. For real applications, implement a UserDetailsService that loads users from the database.
A common production form: users are stored in the database and loaded through a repository:
@Service
public class CustomUserDetailsService
implements UserDetailsService {
private final UserRepository repository;
public CustomUserDetailsService(UserRepository repository) {
this.repository = repository;
}
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
return repository.findByUsername(username)
.map(user -> User.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRole())
.build())
.orElseThrow(() -> new UsernameNotFoundException(
"User tidak ditemukan"));
}
}With this pattern, authentication comes from real data in the database. The password hash still uses BCryptPasswordEncoder.
Set security policy with SecurityFilterChain. Example using form login for a web application and allowing public pages:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/home", "/register")
.permitAll()
.requestMatchers("/api/admin/**")
.hasRole("ADMIN")
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults())
.build();
}Rules are read from top to bottom: public pages are allowed without login, the admin area is restricted to the ADMIN role, and everything else requires authentication. The order of requestMatchers strongly determines the final result.
CSRF protects against attacks that force an authenticated user to send a malicious request. Spring enables it by default with a CSRF token. For stateless REST APIs that use tokens (not cookies), CSRF isn't very relevant and is often disabled:
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated())
.build();For cookie-based web applications with form login, keep CSRF enabled. This decision depends on the authentication mechanism in use — bearer tokens don't need CSRF, session cookies do.
CORS controls which origins can access your API from a browser:
return http
.cors(cors -> cors.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://app.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT"));
config.setAllowedHeaders(List.of("Authorization"));
return config;
}))
.build();Don't open up all origins unless you truly need to — restrict them to the domains you control. In addition, Spring adds security headers automatically: X-Content-Type-Options, Strict-Transport-Security, and frame options, which protect against various browser attacks.
Test the configuration with curl. Without credentials, requests are rejected with status 401:
curl -I http://localhost:8080/api/items
curl -u admin:admin123 http://localhost:8080/api/adminThe curl -u admin:admin123 http://localhost:8080/api/admin command sends basic auth — an Authorization header containing the username and password. The first request without credentials will receive 401 Unauthorized, and the second will only succeed for a user with the ADMIN role.
Episode 12 equipped you with Spring Security basics: understanding the filter chain, configuring the authentication manager with UserDetailsService and PasswordEncoder, setting access rules in SecurityFilterChain, and enabling CSRF, CORS, and security header protection.
Key takeaways:
spring-boot-starter-security locks down every endpoint by default.UserDetailsService loads users; BCryptPasswordEncoder hashes passwords.SecurityFilterChain sets authorization rules with requestMatchers.In the next episode, episode 13, we'll discuss OAuth 2.0 and JWT — the roles of an authorization server vs a resource server, implementing token-based auth with JWT, integrating external identity providers like Keycloak, and best practices for refresh tokens and scopes.