LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 1121 min read

Security Dependencies

Learn spring-boot-starter-security: the default login form, password encoding, a SecurityFilterChain bean, CORS configuration, and testing secured endpoints.

Introduction

The moment an application talks to the outside world, it needs to answer two questions on every request: who is this, and are they allowed to do this? Spring Security answers both, and spring-boot-starter-security is the single dependency that pulls the whole framework in and auto-configures a sane default.

What You Will Learn
  • What spring-boot-starter-security does the moment it is added, with zero configuration.
  • How to hash passwords correctly with BCryptPasswordEncoder.
  • How to define your own SecurityFilterChain bean to control access rules.
  • How to configure CORS for a frontend on a different origin.
  • How spring-security-test lets you test secured endpoints without a real login.

spring-boot-starter-security

Adding this single dependency changes your application's behavior immediately: every endpoint becomes protected, a default login form appears, and a random password is printed to the console on startup. This is Spring Boot's way of making "secure by default" impossible to ignore.

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Console on Startup

Click Run to see what this code prints.

That generated password logs into the default user "user" through the auto-generated login page at /login. It is meant purely as a safety net — real applications replace it with their own user store and rules, which is what the rest of this lesson covers.

Password Encoding

Passwords must never be stored in plain text or with a fast, reversible hash. BCryptPasswordEncoder applies a slow, salted hashing algorithm specifically designed to resist brute-force attacks, and Spring Security registers it as the standard PasswordEncoder bean.

@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Service
public class UserRegistrationService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
public UserRegistrationService(PasswordEncoder passwordEncoder, UserRepository userRepository) {
this.passwordEncoder = passwordEncoder;
this.userRepository = userRepository;
}
public void register(String username, String rawPassword) {
String hashed = passwordEncoder.encode(rawPassword);
userRepository.save(new AppUser(username, hashed));
}
}
Example Hash

Click Run to see what this code prints.

A SecurityFilterChain Bean

Modern Spring Security (5.7+) configures rules through a SecurityFilterChain bean rather than extending WebSecurityConfigurerAdapter. This is where you decide which endpoints are public, which require authentication, and how login works.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**", "/login").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.csrf(csrf -> csrf.disable());
return http.build();
}
}
Behavior

Click Run to see what this code prints.

CSRF and APIs

Disabling CSRF here is appropriate for a stateless REST API secured with tokens (covered in the next lesson). For a traditional server-rendered app with cookie-based sessions and HTML forms, leave CSRF protection enabled — that is exactly what it exists to prevent.

CORS Configuration

When a frontend on one origin (say, http://localhost:5173) calls an API on another (http://localhost:8080), the browser blocks the request unless the server explicitly allows it via CORS headers. Spring Security enforces this at the filter chain level, so CORS needs to be configured alongside your security rules, not separately.

@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:5173"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}

Then reference it from the filter chain with http.cors(Customizer.withDefaults()) so Spring Security actually applies this configuration instead of ignoring it.

Testing Secured Endpoints

spring-security-test adds @WithMockUser and related test annotations, letting you assert access rules in a slice test without standing up a real authentication flow.

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
@WebMvcTest(AdminController.class)
class AdminControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void rejectsAnonymousUser() throws Exception {
mockMvc.perform(get("/api/admin/reports"))
.andExpect(status().is3xxRedirection());
}
@Test
@WithMockUser(roles = "ADMIN")
void allowsAdminUser() throws Exception {
mockMvc.perform(get("/api/admin/reports"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(roles = "USER")
void rejectsNonAdminUser() throws Exception {
mockMvc.perform(get("/api/admin/reports"))
.andExpect(status().isForbidden());
}
}

Common Mistakes

Avoid These Mistakes
  • Shipping the auto-generated console password to production instead of defining a real user store.
  • Storing passwords with a fast hash like MD5 or SHA-256 instead of BCrypt.
  • Disabling CSRF globally on an app that still uses cookie-based session login.
  • Forgetting that .cors() in the filter chain does nothing unless a CorsConfigurationSource bean is also defined.

Frequently Asked Questions

Yes — by default it uses an in-memory user, and you can also define InMemoryUserDetailsManager beans for a fixed set of users during early development.

It embeds a random salt in the output hash itself, which is why encode() is not deterministic but matches() can still verify correctly.

In recent Spring Boot versions it is applied automatically when the starter is on the classpath, but adding it explicitly makes the intent clear and is still common practice.

Summary

spring-boot-starter-security locks everything down by default, and a SecurityFilterChain bean is where you deliberately open it back up. Combined with BCryptPasswordEncoder for storage and spring-security-test for verification, this covers session and form-based security. Next, we look at token-based security with OAuth2 and JWT.

Next Lesson →

OAuth2 & JWT Dependencies