PrepZone Logo
PrepZone

Filter Chain, Authentication, Authorization

How Spring Security protects your endpoints with a filter chain and role-based access.

Why this matters

  • Adding spring-boot-starter-security secures every endpoint by default — understanding the filter chain prevents accidental lockdown or exposure.
  • Authentication (who are you?) and authorization (what can you do?) are separate concerns with distinct configuration points.
  • Security is non-negotiable for production BookStore deployments handling user data and orders.

Add Spring Security

Java
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Every endpoint now requires authentication. A default user user with a random password appears in the startup log.

SecurityFilterChain configuration

Java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/health", "/api/books/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/orders/**").authenticated()
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults());

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
CorsFilter
JwtAuthFilter
AuthorizationFilter
Controller
Each filter handles one concern. Order matters — authentication before authorization.

Authorization rules for BookStore

  • permitAll() — No authentication required. Health checks, public catalog browsing.
  • authenticated() — Any logged-in user. Order placement, profile management.
  • hasRole("ADMIN") — Specific role required. Inventory management, user administration.
  • hasAuthority("SCOPE_read") — Fine-grained permission (OAuth2 scopes).

UserDetailsService

Java
@Service
public class BookStoreUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        User user = userRepository.findByEmail(username)
            .orElseThrow(() -> new UsernameNotFoundException(username));

        return org.springframework.security.core.userdetails.User
            .withUsername(user.getEmail())
            .password(user.getPasswordHash())
            .roles(user.getRole())
            .build();
    }
}

Spring calls loadUserByUsername during login to fetch credentials and roles.

Method-level security

Java
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {}

@Service
public class BookService {
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteBook(Long id) {
        repository.deleteById(id);
    }

    @PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
    public void updateProfile(Long id, UpdateProfileRequest request) { ... }
}

@PreAuthorize evaluates SpEL expressions before method execution — defense in depth beyond URL-level rules.

Password encoding

Never store plain-text passwords:

Java
@Service
public class RegistrationService {
    private final PasswordEncoder encoder;
    private final UserRepository userRepository;

    public User register(RegisterRequest request) {
        User user = new User(
            request.email(),
            encoder.encode(request.password()),
            "USER"
        );
        return userRepository.save(user);
    }
}

BCryptPasswordEncoder hashes with a random salt per password — same input produces different hashes.

Testing secured endpoints

Java
@WebMvcTest(BookController.class)
@Import(SecurityConfig.class)
class BookControllerSecurityTest {
    @Autowired MockMvc mockMvc;

    @Test
    @WithMockUser(roles = "ADMIN")
    void adminCanDelete() throws Exception {
        mockMvc.perform(delete("/api/books/1"))
            .andExpect(status().isNoContent());
    }

    @Test
    void anonymousCannotDelete() throws Exception {
        mockMvc.perform(delete("/api/books/1"))
            .andExpect(status().isUnauthorized());
    }
}

@WithMockUser simulates an authenticated user in tests.

Security context

After authentication, the SecurityContext holds the current user:

Java
@Service
public class OrderService {
    public Order placeOrder(PlaceOrderRequest request) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String userEmail = auth.getName();
        // create order for authenticated user
    }
}

Access it anywhere in the request thread — no need to pass the user through every method parameter.

Quick recall

Everything you need if you only revisit this box.

  • spring-boot-starter-security secures all endpoints by default.
  • SecurityFilterChain configures authentication and authorization rules per URL pattern.
  • UserDetailsService loads user credentials and roles from your data store.
  • @PreAuthorize enforces method-level access control with SpEL expressions.
  • BCryptPasswordEncoder hashes passwords with per-password salts.
  • SecurityContextHolder provides the current authenticated user anywhere in the request thread.

Test yourself

Answer these before moving on — recall is what makes it stick.