Skip to content

Spring Security Fundamentals

flowchart LR
    Client -->|Basic Auth| SecurityFilter
    SecurityFilter --> UserDetailsService
    UserDetailsService --> InMemory["InMemoryUserDetailsManager or Database"]
    SecurityFilter --> Controller
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("user")
.password(passwordEncoder().encode("root"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());
return http.build();
}
}

SecurityFilterChain defines how incoming HTTP requests are secured.

Example:

http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());

anyRequest() must appear only once and last.

Incorrect:

http.authorizeHttpRequests(a -> a.anyRequest().authenticated());
http.authorizeHttpRequests(a -> a.anyRequest().authenticated());

Produces:

Can't configure anyRequest after itself

Spring Security needs to know:

  • Username
  • Password
  • Roles

For demos, users can be stored in memory.

@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("user")
.password(passwordEncoder().encode("root"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}

Instead of memory, implement UserDetailsService that loads users from the database.

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

Generate a password:

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
System.out.println(encoder.encode("root"));

Store the generated hash in the database.

.password("{noop}root")

Never use {noop} in production.

Since httpBasic() is enabled, clients send:

Authorization: Basic base64(user:root)
Terminal window
curl -u user:root http://localhost:8080/employees
  • Authorization
  • Basic Auth
  • Username: user
  • Password: root

Encoded password does not look like BCrypt

Section titled “Encoded password does not look like BCrypt”

Cause:

  • Stored password is plain text
  • BCryptPasswordEncoder is configured

Fix:

  • Encode passwords before storing.
  • Or use {noop} only for testing.

Cause:

Multiple calls to anyRequest().

Fix:

Only one authorizeHttpRequests() chain and one anyRequest().

Application Security Database Security
Spring Security MySQL/PostgreSQL authentication
Controls API access Controls DB login
Users, roles, authorities Database users

These are completely different concerns.

sequenceDiagram
    participant Client
    participant Spring
    participant UserDetailsService

    Client->>Spring: username/password
    Spring->>UserDetailsService: loadUserByUsername()
    UserDetailsService-->>Spring: UserDetails
    Spring->>Spring: Validate password
    Spring-->>Client: 200 or 401
  • Use BCrypt for password storage.
  • Never store plain-text passwords.
  • Keep one SecurityFilterChain.
  • Place anyRequest() last.
  • Use a database-backed UserDetailsService in production.
  • Prefer JWT/OAuth2 for stateless APIs.
  • SecurityFilterChain defines endpoint security.
  • UserDetailsService supplies users during authentication.
  • PasswordEncoder protects stored passwords.
  • BCryptPasswordEncoder requires BCrypt hashes.
  • Spring Security authenticates application users, not database users.