Skip to content
Spring Boot sb security 2 min read

In-Memory Authentication

In-memory authentication stores user accounts in a Map held in memory rather than in a database. It is the fastest way to give your application real, named users with roles, and it is ideal for demos, prototypes, documentation samples, and integration tests where standing up a database would be overkill. For anything production-bound, switch to database authentication.

The UserDetailsService bean

Spring Security looks for a UserDetailsService bean to load users by username. For in-memory users you provide an InMemoryUserDetailsManager populated with UserDetails objects.

import org.springframework.context.annotation.*;
import org.springframework.security.core.userdetails.*;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
public class SecurityConfig {

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        UserDetails user = User.builder()
                .username("alice")
                .password(encoder.encode("password"))
                .roles("USER")
                .build();

        UserDetails admin = User.builder()
                .username("bob")
                .password(encoder.encode("admin123"))
                .roles("USER", "ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

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

Defining a UserDetailsService bean replaces the default generated user account. Spring Boot’s auto-configured DaoAuthenticationProvider automatically uses your bean together with the PasswordEncoder.

User.builder()

User.builder() is the canonical way to construct a UserDetails. The important methods:

MethodPurpose
.username(...)The login name
.password(...)The encoded password (never plaintext)
.roles("USER")Adds authorities with the ROLE_ prefix → ROLE_USER
.authorities("orders:read")Adds authorities verbatim, no prefix
.disabled(true)Marks the account disabled
.accountLocked(true)Marks the account locked

Warning: .roles("USER") and .authorities("ROLE_USER") produce the same result, but do not mix them. .roles("ROLE_USER") throws — roles() adds the prefix itself, so pass "USER".

Encoding passwords

Even for in-memory users you must store an encoded password. Use the injected PasswordEncoder as shown above. The lazy shortcut User.withDefaultPasswordEncoder() exists but is deprecated and unsafe — avoid it.

// Good — encode with the configured encoder
.password(encoder.encode("password"))

// Avoid — deprecated, embeds plaintext intent in code
User.withDefaultPasswordEncoder().username("x").password("password").roles("USER").build();

See Password Encoding for why this matters.

Putting it together

Pair the user store with a SecurityFilterChain that defines who can reach what:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/admin/**").hasRole("ADMIN")
            .requestMatchers("/api/**").hasRole("USER")
            .anyRequest().authenticated())
        .httpBasic(Customizer.withDefaults());
    return http.build();
}

Request as the USER account:

curl -u alice:password http://localhost:8080/api/orders

Output:

HTTP/1.1 200 OK

Request the admin path as a non-admin:

curl -u alice:password http://localhost:8080/api/admin/stats

Output:

HTTP/1.1 403 Forbidden

Using it in tests

In-memory users shine in @SpringBootTest/@WebMvcTest. Combine with @WithMockUser to skip the password round-trip entirely:

@WebMvcTest(OrderController.class)
class OrderControllerTest {

    @Autowired MockMvc mvc;

    @Test
    @WithMockUser(roles = "ADMIN")
    void adminCanSeeStats() throws Exception {
        mvc.perform(get("/api/admin/stats"))
           .andExpect(status().isOk());
    }
}

Adding users at runtime

InMemoryUserDetailsManager implements UserDetailsManager, so you can create, update, and delete users while the app runs (still in memory only):

manager.createUser(User.builder()
        .username("carol")
        .password(encoder.encode("temp"))
        .roles("USER")
        .build());

Tip: Because the store lives in heap memory, every restart resets it and it does not work across multiple instances. Treat it strictly as non-persistent.

Last updated June 13, 2026
Was this helpful?