Tutorials Logic, IN info@tutorialslogic.com

Spring Security Authentication, Authorization, Filters

Security Filter Chain

Spring Security protects servlet applications through filters that run before the controller. Authentication establishes who is making the request; authorization decides whether that identity may perform the requested action. In current Spring Security, SecurityFilterChain defines the web rules and AuthorizationManager is the central authorization contract.

This lesson assumes basic Spring MVC knowledge. After it, you can read a filter-chain configuration in request order, distinguish 401 from 403 behavior, protect routes with least-privilege rules, and test access for anonymous and authenticated users.

What is Spring Security?

Spring Security is a powerful and highly customizable authentication and access-control framework for Java applications. It is the de-facto standard for securing Spring-based applications. It provides:

  • Authentication (who are you?)
  • Authorization (what can you do?)
  • Protection against common attacks (CSRF, session fixation, clickjacking)
  • Integration with OAuth2, JWT, LDAP, and more

SecurityFilterChain Configuration

SecurityFilterChain Configuration
package com.example.config;

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // Authorization rules
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/home", "/register", "/css/**", "/js/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated()
            )
            // Form login
            .formLogin(form -> form
                .loginPage("/login")
                .loginProcessingUrl("/login")
                .defaultSuccessUrl("/dashboard", true)
                .failureUrl("/login?error=true")
                .permitAll()
            )
            // Logout
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout=true")
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID")
                .permitAll()
            )
            // HTTP Basic auth (for REST APIs)
            // .httpBasic(Customizer.withDefaults())
            // CSRF protection (disable for REST APIs)
            // .csrf(csrf -> csrf.disable())
            ;

        return http.build();
    }

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

UserDetailsService

Custom UserDetailsService

Custom UserDetailsService
package com.example.security;

import com.example.entity.User;
import com.example.repository.UserRepository;
import org.springframework.security.core.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.*;
import org.springframework.stereotype.Service;
import java.util.*;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));

        // Convert roles to GrantedAuthority
        List<GrantedAuthority> authorities = List.of(
                new SimpleGrantedAuthority("ROLE_" + user.getRole().name())
        );

        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(), // BCrypt hashed password
                authorities
        );
    }
}

// Registration service - hash password before saving
@Service
public class RegistrationService {
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public RegistrationService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    public User register(String username, String email, String rawPassword) {
        User user = new User();
        user.setUsername(username);
        user.setEmail(email);
        user.setPassword(passwordEncoder.encode(rawPassword)); // Hash!
        user.setRole(User.Role.USER);
        return userRepository.save(user);
    }
}

Method Security

@PreAuthorize and @PostAuthorize

@PreAuthorize and @PostAuthorize
// Enable method security in config:
// @EnableMethodSecurity(prePostEnabled = true)

@Service
public class AdminService {

    // Only ADMIN role can call this
    @PreAuthorize("hasRole('ADMIN')")
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    // User can only access their own data
    @PreAuthorize("hasRole('ADMIN') or #username == authentication.name")
    public User getUserProfile(String username) {
        return userRepository.findByUsername(username).orElseThrow();
    }

    // Check after method returns
    @PostAuthorize("returnObject.username == authentication.name or hasRole('ADMIN')")
    public User getUser(Long id) {
        return userRepository.findById(id).orElseThrow();
    }

    // Secure with SpEL expression
    @PreAuthorize("hasRole('ADMIN') and #user.role != 'ADMIN'")
    public void deleteUser(User user) {
        userRepository.delete(user);
    }
}

Request Protection

A DelegatingFilterProxy connects the servlet container to Spring Security. FilterChainProxy selects the first SecurityFilterChain whose matcher applies, then invokes its security filters in order. Authentication filters must run before authorization because the authorization decision needs the current SecurityContext.

Write specific request matchers before broad ones and finish with an explicit fallback such as anyRequest().authenticated() or denyAll(). A visible fallback prevents a newly added endpoint from becoming public merely because no rule mentioned it.

Session and CSRF

Browser applications commonly keep authentication in a session. Because browsers automatically attach session cookies, a forged cross-site request can submit an unwanted state change; CSRF protection addresses that risk with a token. Do not disable CSRF simply to make a form or test pass. Configure the client to send the token, or justify a stateless non-browser API design.

For stateless bearer-token APIs, configure session creation deliberately and validate tokens on every request. CORS and CSRF solve different problems: CORS controls which browser origins may read or send cross-origin requests, while CSRF protects authenticated state-changing actions from forged browser requests.

Access Diagnosis

A 401 response means usable authentication is missing or invalid. A 403 means the request reached an authorization decision but access was denied, or a required CSRF token was absent. Enable focused Spring Security logging in development and inspect which chain matched, which authentication was created, and which authorization rule denied the request.

Use spring-security-test with MockMvc to state the caller explicitly. Test anonymous access, a permitted role, a forbidden role, and CSRF behavior for writes. These tests protect the route contract when mappings or rules change.

Before you move on

Access Review

4 checks
  • Separate authentication failures from authorization failures.
  • Order request rules from specific to broad and define a secure fallback.
  • Keep CSRF protection for session-based browser writes.
  • Test anonymous, permitted, forbidden, and CSRF cases.

Security Misconfigurations

  • Using a broad permitAll rule before protected matchers.

    Place narrow public routes first and require authentication or deny access by default.
  • Treating CORS as CSRF protection.

    Configure each control for its separate browser threat.
  • Checking roles only in the user interface.

    Enforce authorization on the server for every protected operation.

Try this next

Prove the Rules

0 of 2 completed

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.