404 Not Found

404 Not Found


nginx

Spring Security Basics

Spring Security is the industry standard for Java security—with its trio of filter chains, authentication, and authorization—it safeguards API security.

1. What You'll Learn


2. A Product Manager's True Story

(1) Pain Point: Unprotected APIs

During a product review meeting, Charlie discovered that none of OrderFlow's APIs were authenticated at all—anyone could call the APIs to create orders or delete products. To make matters worse, Bob had exposed the Actuator endpoint to the public internet, allowing scanners to obtain production database information. Charlie demanded that security controls be implemented immediately, and Alice needed the fastest possible solution.

(2) Spring Security Solution

Spring Security can secure your API with just a few lines of configuration:

JAVA
@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

(3) Revenue

After Alice implemented authentication and authorization for OrderFlow using Spring Security, the management API was accessible only to users with the ADMIN role, the standard API required users to log in, and Actuator endpoints were restricted to internal network access. Charlie was satisfied with the security compliance.


3. Spring Security Architecture

(1) Filter Chain

100%
sequenceDiagram
    participant Client
    participant Chain as SecurityFilterChain
    participant Auth as Authentication Filter
    participant Authz as Authorization Filter
    participant Controller

    Client->>Chain: HTTP Request
    Chain->>Auth: 1. Authentication
    alt Credentials Invalid
        Auth-->>Client: 401 Unauthorized
    end
    Auth->>Authz: 2. Authorization
    alt Access Denied
        Authz-->>Client: 403 Forbidden
    end
    Authz->>Controller: 3. Forward to Controller
    Controller-->>Client: 200 OK
Key Concepts Description
SecurityFilterChain An ordered chain of filters, through which each request passes sequentially
Authentication Verification: Confirm "Who You Are"
Authorization Authorization: Confirm "What You Can Do"
SecurityContext Security context containing the current user's information
GrantedAuthority Permissions/Role Information

(2) Relationships Among Core Components

Component Responsibilities Interface
AuthenticationManager Authentication Manager authenticate()
ProviderManager AuthenticationManager default implementation Delegates to AuthenticationProvider
UserDetailsService Loading user information loadUserByUsername()
PasswordEncoder Password Encoding encode() / matches()

4. SecurityFilterChain Configuration

(1) ▶ Example: Basic Security Configuration

JAVA
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/products/**").permitAll()
                .requestMatchers("/api/v1/orders/**").authenticated()
                .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("https://orderflow.example.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        config.setAllowedHeaders(List.of("*"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return source;
    }
}

Output:

TEXT
// Execution Successful
Configuration Option Description REST API Recommendation
CSRF Cross-Site Request Forgery Protection Disabled (not required for stateless APIs)
CORS Cross-Origin Resource Sharing Configure Allowed Domains
Session Session Management STATELESS (Stateless)
httpBasic HTTP Basic Authentication For Development/Testing
formLogin Form Login For traditional web applications

5. In-Memory User Authentication

(1) ▶ Example: InMemoryUserDetailsManager

JAVA
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        UserDetails admin = User.builder()
            .username("alice")
            .password(encoder.encode("admin123"))
            .roles("ADMIN", "CUSTOMER")
            .build();

        UserDetails customer = User.builder()
            .username("bob")
            .password(encoder.encode("customer123"))
            .roles("CUSTOMER")
            .build();

        return new InMemoryUserDetailsManager(admin, customer);
    }

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

Output:

TEXT
// Execution Successful
PasswordEncoder Security Use Cases
BCryptPasswordEncoder High (includes salt value) Recommended for production environments
Argon2PasswordEncoder Highest (GPU Resistance) High Security Requirements
NoOpPasswordEncoder None (plaintext) For testing purposes only
🔒 Security: Never use NoOpPasswordEncoder in a production environment. BCrypt is the minimum standard; Argon2 is a higher standard.

(2) ▶ Example: Test Certification

BASH
# Access without credentials -> 401
curl http://localhost:8080/api/v1/orders

# Access with customer credentials -> 200
curl -u bob:customer123 http://localhost:8080/api/v1/orders

# Access admin endpoint with customer -> 403
curl -u bob:customer123 http://localhost:8080/api/v1/admin/dashboard

# Access admin endpoint with admin -> 200
curl -u alice:admin123 http://localhost:8080/api/v1/admin/dashboard

Output:

TEXT
{"status":"ok","data":{}}

6. Method-Level Authorization

(1) ▶ Example: @PreAuthorize Method Authorization

JAVA
@Service
public class OrderService {

    @Transactional(readOnly = true)
    @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER')")
    public Order getOrder(Long orderId) {
        return orderRepository.findById(orderId).orElseThrow();
    }

    @Transactional
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteOrder(Long orderId) {
        orderRepository.deleteById(orderId);
    }

    @Transactional
    @PreAuthorize("hasRole('ADMIN') or #customerId == authentication.principal.id")
    public List<Order> getCustomerOrders(Long customerId) {
        return orderRepository.findByCustomerId(customerId);
    }
}

Output:

TEXT
// Execution Successful
Notes Features Use Cases
@PreAuthorize SpEL expression, checks before accessing the method Most flexible, recommended
@PostAuthorize SpEL expression; check after the method is executed Permissions must be determined based on the return value
@Secured Character List, does not support SpEL Simple Character Check
@RolesAllowed JSR-250 Standard Annotations Cross-Framework Compatibility
📌 Key Point: To use method-level authorization, add @EnableMethodSecurity to the configuration class.

(2) ▶ Example: @EnableMethodSecurity configuration

JAVA
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
    // ... SecurityFilterChain and UserDetailsService beans
}

Output:

TEXT
// Execution Successful

7. Quick Reference for Path Authorization Rules

Matching Method Description Example
requestMatchers(String) Ant-style path matching /api/v1/orders/**
requestMatchers(HttpMethod, String) Restricted HTTP Methods POST /api/v1/orders
anyRequest() Matches all requests Placed last as a catch-all
Authorization Method Description
permitAll() Open to everyone
authenticated() Authentication Required
hasRole("ADMIN") Requires the ADMIN role
hasAnyRole("A", "B") Any character needed
denyAll() Block All Access

(1) ▶ Example: Restricting API Access by Role

JAVA
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.csrf(csrf -> csrf.disable())
        .authorizeHttpRequests(auth -> auth
            .requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll()
            .requestMatchers(HttpMethod.POST, "/api/v1/products").hasRole("ADMIN")
            .requestMatchers(HttpMethod.PUT, "/api/v1/products/**").hasRole("ADMIN")
            .requestMatchers(HttpMethod.DELETE, "/api/v1/products/**").hasRole("ADMIN")
            .requestMatchers("/api/v1/orders/**").hasAnyRole("ADMIN", "CUSTOMER")
            .requestMatchers("/actuator/**").hasRole("ADMIN")
            .anyRequest().authenticated()
        )
        .httpBasic(Customizer.withDefaults());
    return http.build();
}

Output:

TEXT
// Execution Successful

8. Comprehensive Example: OrderFlow Security Configuration

JAVA
// SecurityConfig.java
package com.orderflow.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll()
                .requestMatchers("/api/v1/orders/**").hasAnyRole("CUSTOMER", "ADMIN")
                .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers("/actuator/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        var admin = User.builder()
            .username("alice").password(encoder.encode("admin123"))
            .roles("ADMIN", "CUSTOMER").build();
        var customer = User.builder()
            .username("bob").password(encoder.encode("pass123"))
            .roles("CUSTOMER").build();
        return new InMemoryUserDetailsManager(admin, customer);
    }

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

// OrderController with method-level security
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {

    @GetMapping
    @PreAuthorize("hasAnyRole('CUSTOMER', 'ADMIN')")
    public List<Order> listOrders() { /* ... */ }

    @GetMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN') or @orderSecurity.isOwner(#id, authentication)")
    public Order getOrder(@PathVariable Long id) { /* ... */ }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteOrder(@PathVariable Long id) { /* ... */ }
}

❓ FAQ

Q Why should CSRF be disabled for REST APIs?
A CSRF protection relies on cookies and sessions. REST APIs are typically stateless (using token-based authentication) and do not use cookies, so CSRF attacks cannot succeed; therefore, CSRF protection can be disabled.
Q What is the difference between hasRole and hasAuthority?
A hasRole("ADMIN") automatically adds the "ROLE_" prefix to check for the ROLE_ADMIN permission. hasAuthority("ADMIN") does not add a prefix and directly checks the ADMIN permission. We recommend using hasRole consistently.
Q Can InMemoryUserDetailsManager be used in production?
A No. In-memory user management is intended for development and testing only. In a production environment, you need to use a custom UserDetailsService to load user information from a database.
Q How do I retrieve the currently logged-in user's information?
A There are three ways: 1) SecurityContextHolder.getContext().getAuthentication(); 2) Injecting parameters into a Controller method Principal principal; 3) @AuthenticationPrincipal UserDetails user. We recommend the third method.
Q Can the order of Spring Security filters be customized?
A Yes, you can control the order of the SecurityFilterChain using the @Order annotation. Multiple SecurityFilterChains can be configured to match different request paths.
Q Which should I choose, httpBasic or formLogin?
A Use httpBasic or Bearer Token for REST APIs; use formLogin for traditional web applications. This lesson uses httpBasic as an introduction; subsequent lessons will switch to JWT.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty: ⭐): Configure Spring Security for OrderFlow to implement permitAll for product queries, authenticated for order operations, and hasRole("ADMIN") for administrative APIs. Test using HTTP Basic authentication and in-memory users.

  2. Advanced Exercise (Difficulty ⭐⭐): Implement @PreAuthorize method-level authorization—users can only view their own orders, while ADMIN can view all orders. Hint: Create an OrderSecurity helper bean and reference it in SpEL.

  3. Challenge (Difficulty: ⭐⭐⭐): Implement a custom UserDetailsService to load user and role information from the database (JPA), replacing the InMemoryUserDetailsManager. Consider security design for password storage and the user registration process.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏