Week 10

Security Mindset

Threat Modeling Basics

Common Backend Vulnerabilities

Handling Sensitive Data

Hashing vs. Encryption

How to Store Passwords

Authentication & Authorization

Spring Security

Spring Security JWT authentication

Practice

Assignment

Backend Track

What is Spring Security?

From its name, Spring Security is the security framework for Spring applications. It is a separate library that hooks into your Spring Boot app and takes over the Authentication & Authorization job you'd otherwise have to build by hand on every endpoint, and provides a highly configurable, declarative approach built right into the Spring ecosystem.

Without a framework, you would write the same checks over and over: read a header, verify a token, look up the user, check their role, return 401 or 403… in every single controller. That is repetitive and easy to get wrong, and security bugs are the most expensive kind.

Spring Security moves all of that to one central place. Requests are checked before they ever reach your controller, so your controller code can assume "if I'm running, the caller is already allowed to be here." It also ships with safe defaults for many of the attacks you read about earlier (it sets security headers, protects against CSRF for browser sessions, integrates BCrypt, and more).

<aside> πŸ’­

Mental model: Spring Security is a security guard standing at the door of your application. Every request has to show ID and prove it's allowed in before it reaches the room (your controller) it's trying to enter.

</aside>

Adding the spring-boot-starter-security dependency

Maven (pom.xml):

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

Spring Security works as a chain of servlet filters that intercept every request before it reaches your controllers. This makes it easy to enforce security rules consistently across the entire application without scatteringΒ ifΒ checks throughout your business logic.

The security filter chain

To understand Spring Security and how requests are intercepted, you need one new concept: the filter.

In a Java web application, before a request reaches your controller, it passes through a chain of filters. A filter is just a piece of code that can look at the request, do something with it, and then either pass it along to the next filter or stop it. Think of filters as a series of checkpoints the request walks through in order.

Spring Security works by inserting its own filters into that chain. This collection of security filters is called the security filter chain.

image.png

Each filter has a single responsibility. Some examples of what filters in the chain do:

If any checkpoint decides the request shouldn't continue, the chain stops the request right there and returns an error response (401 Unauthorized or 403 Forbidden) β€” your controller is never even called.

Two new terms you'll see:

You don't write these filters yourself β€” Spring Security provides them. Your job is to tell the chain what your rules are, which is exactly what the next sections cover.

Default behaviour: everything is protected

By default, Spring Security protects every endpoint. Just adding the dependency changes your app's behaviour. Nothing is public until you say so. This is a deliberate, safe default: it's much better to accidentally lock something that should be open (you'll notice immediately) than to accidentally leave something open that should be locked (an attacker notices instead of you).

Try to call any endpoint, and you will get: β†’ 401 Unauthorized

Spring Boot also does two more things by default to give you a way in:

Using generated security password: 1f8e2c5a-6b3d-44a7-9f0e-2d1c3b4a5e6f

So out of the box, you can log in as user with that printed password. This is only meant for getting started β€” it is not a real login system. In a real app you replace all of this with your own configuration, which is the rest of this chapter.

<aside> πŸ’‘

With Spring Security, security is opt-out, not opt-in. You start fully locked down and then open up exactly the doors you want.

</aside>

Configuring the SecurityFilterChain bean

To replace the defaults with your own rules, you define a SecurityFilterChain bean inside a configuration class. This is the single most important piece of Spring Security to understand β€” it's where you describe which endpoints are public, which require login, and which require specific roles.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/auth/**").permitAll()                 // login & register: public
                .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll() // anyone can browse
                .anyRequest().authenticated()                            // everything else: must be logged in
            );

        return http.build();
    }
}

Let's read this the way Spring Security reads it β€” top to bottom, first match wins:

A few things worth knowing about this configuration:

Because we're building a stateless JWT API (not a browser app with sessions), we'll add two more settings to this chain in section 8: turning off CSRF and turning off server-side sessions. We'll explain why there, since it depends on JWT.

Protecting endpoints by role

Authentication tells you who the user is. Authorization by role tells you what they may do. In Spring Security, a user carries a set of authorities, and a role is just an authority with a conventional ROLE_ prefix.

You restrict endpoints to roles right inside the same SecurityFilterChain:

http
    .authorizeHttpRequests(auth -> auth
        .requestMatchers("/auth/**").permitAll()
        .requestMatchers("/api/admin/**").hasRole("ADMIN")          // only admins
        .requestMatchers("/api/orders/**").hasAnyRole("USER", "ADMIN") // users or admins
        .anyRequest().authenticated()
    );

The one gotcha everybody hits: the ROLE_ prefix. When you write hasRole("ADMIN"), Spring Security automatically looks for the authority ROLE_ADMIN. So:

If you build users with .roles("ADMIN") (shown in the next section), Spring adds the ROLE_ prefix for you, so it lines up correctly with hasRole("ADMIN"). Mixing these up is the number-one reason "I'm logged in but I keep getting 403."

401 vs 403 β€” know the difference:

Method-level security: @PreAuthorize and @Secured

URL rules in the filter chain are great for broad strokes, but sometimes the rule belongs right next to the code it protects β€” for example, on a service method that several controllers might call. For that, Spring Security offers method-level security.

First, switch it on with @EnableMethodSecurity (add this on a @Configuration class β€” your SecurityConfig is a good home):

import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity            // enables @PreAuthorize / @PostAuthorize
// @EnableMethodSecurity(securedEnabled = true)  // also enables @Secured
public class SecurityConfig { /* ... */ }

Now you can annotate methods.

@PreAuthorize runs before the method and accepts a SpEL expression (Spring Expression Language), which makes it very flexible:

@Service
public class ProductService {

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteProduct(Long id) {
        // only admins ever get here
    }

    // A user may only read their OWN profile.
    // `authentication.name` is the logged-in username; `#username` is the method argument.
    @PreAuthorize("#username == authentication.name or hasRole('ADMIN')")
    public Profile getProfile(String username) {
        // ...
    }
}

@Secured is the older, simpler annotation β€” it only checks roles and has no SpEL. Note you must write the full ROLE_ prefix here:

@Secured("ROLE_ADMIN")
public void promoteUser(Long id) {
    // ...
}

Which to use? Prefer @PreAuthorize β€” it does everything @Secured does and a lot more (combine roles, compare against method arguments, call your own beans). @Secured mainly shows up in older codebases. When a method-level check fails, Spring throws an AccessDeniedException, which surfaces as a 403.

Filter-chain rules vs method annotations β€” when to use which? Use URL rules in the SecurityFilterChain for coarse, route-based protection (/api/admin/** is admin-only). Use method security when the rule depends on the data β€” "only the owner of this resource," "only an admin can delete." Many apps use both.