Study Notes
Spring Boot Frameworkcore-concepts

Spring Security — Basic Auth

How an API stops guarding nothing and starts checking who you are and what you may do. Authentication vs authorization, the Spring Security filter chain, a request's journey through the guard, Basic Auth setup, and role-based access control. Includes microexercises and interview Q&A.

40 min read·verified Spring Boot 4.x·reviewed 2026-08-28

After this you should be able to

  • Separate authentication from authorization
  • Configure role-based access
SecurityBasic AuthRBAC

Java · Spring Security

How a Spring Boot API stops guarding nothing and starts checking who you are and what you may do, on every single request, before your code ever runs.

Java-301 · Session 7  ·  Updated August 2026  ·  Read ~16 min


Choose your route#

Problem: an API must decide who made a request and what that identity may do. Spend 40 minutes configuring a guard or 15 minutes practising the explanation.

Learning goal

Recall first: distinguish a request’s credentials from its permissions. REST foundations help, but they are only guidance.

  • Separate authentication from authorization.
  • Configure role-based access.
  • Trace a request through the security filter chain.

Shared core: prove identity, then enforce policy#

Spring Security intercepts a request before the controller, establishes an authenticated principal when credentials are valid, then evaluates authorization rules for the requested resource.

Closest confusion: authentication answers “who are you?”; authorization answers “may you do this?” A 401 usually asks for valid credentials, while a 403 means a known identity lacks permission.

Failure trace: hasRole("ROLE_ADMIN") applies the prefix twice in common configuration, so an apparently admin user gets 403. Use hasRole("ADMIN") or hasAuthority("ROLE_ADMIN").

Decision rule: place specific path rules before broad ones, use the smallest required authority, and keep credentials out of application logs.

Version note: configuration reflects current Spring Security style reviewed with Spring Boot 4.x on 2026-08-28.

Learn deeply: guard two routes#

Mission (25 minutes): configure Basic Auth for a small API: a public health route, an authenticated profile route, and an admin-only route. Likely files: security configuration and controller tests.

Acceptance checks: health works anonymously; missing credentials receive 401; a normal user gets 403 on admin; an admin receives the intended response.

  1. Write route rules from most specific to most general.
  2. Create two local users or a test user source.
  3. Prove every status code with curl or an integration test.
Solution shape, not a script

Build one SecurityFilterChain, explicitly permit the small public surface, and let every other route require authentication before adding role rules.

Transfer: add a manager route and decide whether a role or a fine-grained authority describes the policy more honestly.

Prepare for interviews: explain the guard#

Two-minute summary: the filter chain authenticates before the controller and authorization rules decide access. Basic Auth transports credentials each request, so it is a simple teaching boundary rather than a universal production choice.

Explain in 60 seconds: distinguish 401 and 403 with one request path.

Primary question: walk through a protected Spring request. Follow-ups: why order matchers, and when would you choose authorities over roles?

Debug exercise: an ADMIN user receives 403 despite having ROLE_ADMIN. Locate the prefix mismatch and give two valid configurations.

Strong-answer outline

Start with filter-chain placement, separate identity from permission, use a concrete status outcome, then mention least privilege and credential transport trade-offs.

Timed mock: design access for public docs, authenticated users, and admins in 90 seconds.

Exit check#

  1. What question does authentication answer?
  2. What status usually represents missing valid credentials?
  3. Why must specific matchers come first?
  4. What prefix does hasRole add?
  5. Where does the filter chain run relative to a controller?

Learn next: Use separate authentication from authorization as the base, then explain jwt verification in JWT Authentication with Spring Security. Interview next: Use separate authentication from authorization as the base, then name the main application layers in The Layer Map.

Continue this path · Switch paths from this topic · Review first: request flow

// the big idea

Spring Security wraps your app in a chain of filters that intercepts every HTTP request before it reaches your controller. Two questions get answered there, in order: authentication (who are you?) and authorization (what are you allowed to do?). Security lives in that chain, not scattered through your business logic.

1 · Notebook summaryCopy this by hand#

Dense, telegraphic, liftable. Everything here is derived from the detailed notes below.

Why secure APIs#

  • Unsecured API = open door → data leak (Panera Bread, 2018: names, emails, DOB, card digits, ~8 mo)
  • Fallout → identity theft · financial loss · phishing · reputational + legal + operational damage
  • 💡 TIP security is a request-layer concern → enforce before the controller, not inside it

Common attacks → prevent#

AttackWhatPrevent
MitMintercept client↔APIHTTPS / TLS
SQL injectionmalicious SQL in queryparameterized queries, validation
XSSscript injected in responsesanitize + validate input
DDoSflood of requestsrate limit, scale
Brute forceguess credentialslockout, strong pwd
  • Doodle: laptop —(scissors cutting the wire)→ server = MitM.
  • SQLi tell: ' OR '1'='1 → condition always true → dumps whole table

AuthN vs AuthZ#

AuthenticationAuthorization
Askswho are you?what may you do?
Checksidentity (credentials)privileges (roles)
Orderfirstafter
  • Doodle: college ID = gets you in the gate (authN); doesn't seat you in principal's cabin (authZ).

Spring Security architecture#

  • Security Filter Chain → ordered filters, first thing every request hits; talks to Auth Manager
  • Authentication Manager → coordinator; delegates to providers
  • Authentication Provider → does the work; Dao(db+UserDetailsService) · Jwt · custom
  • UserDetailsService → loadUserByUsername() → fetch user from DB
  • PasswordEncoder → BCrypt (salts, use this) vs NoOp (plaintext, never)
  • SecurityContextHolder → holds Authentication obj for the request; Principal = current user
  • Doodle: bouncer(chain) → manager(radios) → worker(provider) → checks ID book(UserDetailsSvc) + PIN pad(encoder).

Request lifecycle#

  • Request → Filter Chain → (not logged in?) Auth Manager → Auth Provider → PasswordEncoder + UserDetailsService → DB → success/401 → store Authentication in SecurityContextHolder → Controller

Basic Auth setup#

  • start.spring.io → deps: Spring Web + Spring Security
  • ⭐ TRICK adding the dep secures every endpoint by default; user=user, password printed in terminal
  • Run ./gradlew bootRun → hit endpoint → login prompt
  • Basic Auth wire: header Authorization: Basic base64(user:pass) on every request
  • ⚠ PITFALL Basic Auth is base64, not encryption → only safe over HTTPS

RBAC#

  • Users get roles (ADMIN / USER); roles carry permissions; gate endpoints by role
  • Why → security · simpler mgmt · consistency · compliance
  • ⚠ PITFALL hasRole("X") auto-adds ROLE_ → pass "ADMIN" not "ROLE_ADMIN"

Test w/ Postman#

  • register USER + ADMIN → Authorization tab → Basic Auth → creds sent each request
  • USER→/ ok, USER→/admins 403; ADMIN→both ok; bad creds 401
  • 401 = who are you? (authN fail) · 403 = I know you, not allowed (authZ fail).

2 · Detailed study referenceThe lesson, in full#

In 2018, anyone who knew the shape of one web address could read the personal records of millions of Panera Bread customers: names, email and home addresses, birthdays, the last four digits of their cards. The data sat behind an API endpoint that simply never asked the visitor to prove anything. It leaked, in the open, for at least eight months. Nobody had to break in, because there was no lock on the door. That is the whole subject of this lesson: the lock, where it hangs, and who is allowed through it.

2.1Why an API needs a guard at all#

An API endpoint is a door into your data, and by default it is unlocked. A web API takes requests over the network and hands back data or performs actions. If nothing checks the visitor, then anyone who can name the address gets whatever is behind it. The Panera leak was not a clever hack; it was the absence of a check.

Analogy · recall

Think of your API as a building full of filing cabinets. Each endpoint is a door. Security is the front desk that checks a badge before anyone walks the halls. Skip the desk and every cabinet is public — which is precisely what happened at Panera.

The consequences of skipping the check are not abstract. Leaked personal data feeds identity theft and fraud; exposed financial data enables direct theft; leaked contact lists power targeted phishing; and beyond the victims, the company itself absorbs reputational damage, legal fines for breaking data-protection rules, and the operational cost of cleaning up instead of building. The takeaway the slides press hard: securing endpoints is not optional polish, it is a baseline requirement for any real application.

The attacks a guard defends against#

Five common attack shapes come up. You do not implement defenses against all of them today, but you should recognize each and name its standard prevention.

AttackWhat the attacker doesStandard prevention
Man-in-the-MiddleSits between client and API, reading or altering traffic — e.g. sniffing a banking app's calls on public Wi-Fi.Encrypt the channel with HTTPS / TLS.
SQL InjectionSlips SQL into an input so the query does more than intended.Parameterized queries + input validation.
Cross-Site ScriptingPlants a script that runs in another user's browser (e.g. via a comment) to steal their session.Sanitize and validate all user input.
DDoSFloods the API with traffic until real users can't get through.Rate limiting, traffic analysis, scalable infra.
Brute ForceScripts thousands of password guesses at a login endpoint.Account lockouts, strong-password rules.
💡 TIP · what TLS vs HTTPS actually means

TLS (Transport Layer Security) is the general protocol that encrypts a network connection; it is the successor to the now-insecure SSL. HTTPS is just HTTP running over TLS. So "use HTTPS" and "use TLS" point at the same defense for web traffic — an encrypted channel an eavesdropper can't read.

⚠ PITFALL · the SQL injection tell

The classic payload is name=chair' OR '1'='1. The appended OR '1'='1' is a condition that is always true, so a query meant to find one product returns the entire table. Any time user text is pasted straight into a query string, this door is open. Parameterized queries close it because the input can never change the query's structure.

2.2Authentication vs authorization#

Authentication proves who you are; authorization decides what you're allowed to do. They sound similar and are constantly confused, but they answer different questions and happen in a fixed order — you cannot decide someone's privileges until you know who they are.

Start from the everyday version. You can Google-search without logging in, but you cannot like a YouTube video or edit a LinkedIn post without one. Some actions need to know who is acting; others don't. And once the system knows you, a second gate opens: on an e-commerce site, Ram may add products to his own cart, but not to Shyam's. Knowing his identity (authentication) is not the same as being permitted the action (authorization).

Analogy · recall

A student's college ID proves who they are — that's authentication, and it gets them through the front gate. It does not entitle them to sit in the principal's cabin or in a class they never enrolled in. That second question — what your identity permits — is authorization.

Authentication

Who are you?#

Verifies identity by checking credentials (username + password). Runs first. Fails with 401 Unauthorized.

Authorization

What may you do?#

Grants or denies access to a resource based on your privileges (roles). Runs after identity is known. Fails with 403 Forbidden.

you Authentication who are you? fail → 401 Authorization what may you do? fail → 403 resource
Two gates, always in this order — identity first, privileges second.

2.3What Spring Security is#

Spring Security is a framework that stands guard in front of your Spring Boot app, checking every request before your own code sees it. It watches who accesses the site, verifies identities, and enforces access rules — the exact set of problems the section above laid out.

To see where it plugs in, you need one fact about how Spring Boot handles web traffic. Spring Boot uses Spring MVC underneath, and in Spring MVC every incoming HTTP request funnels through a single dispatch box (the DispatcherServlet) that then routes it to the right controller method. Spring Security inserts a stack of filter classes before that dispatch box. So a request is inspected and, if needed, rejected, before it is ever handed to your controller.

HTTP request→ Security filters→ DispatcherServlet→ your Controller
Analogy · recall

Spring Security is a security desk in the lobby of an office (the DispatcherServlet is the elevator to the floors). Every visitor passes the desk first. Cleared visitors ride up to the right floor; the rest never reach the elevator.

2.4The pieces inside the guard#

Spring Security is not one object; it is a small cast of collaborators, each with one job. Learn the cast and the architecture diagram stops being intimidating. You configure these once, at the start of a project — the goal is a high-level grasp of how they hand off to each other, not memorizing every setter.

Security Filter Chain#

the backbone

An ordered series of filters every request passes through. Handles authentication, authorization, sessions and more, then talks to the Authentication Manager. Order matters — each filter owns one concern.

Authentication Manager#

the coordinator

The central component that verifies identity. It doesn't do the checking itself; it delegates to one or more Authentication Providers.

Authentication Provider#

the worker

Actually performs authentication. Common types: DaoAuthenticationProvider (DB via UserDetailsService), JwtAuthenticationProvider (validates JWTs), or a custom one.

UserDetailsService#

the lookup

An interface with one method, loadUserByUsername(), that fetches a user's details (username, password, roles) — usually from the database via a repository.

PasswordEncoder#

the hasher

Hashes and verifies passwords so they're never compared in plaintext. BCryptPasswordEncoder is the recommended default; NoOpPasswordEncoder stores plaintext and must never ship.

SecurityContextHolder#

the memory

Holds the security context for the request. After a successful login it stores the Authentication object; the Principal inside it is the current user (name, authorities).

💡 TIP · why "DAO" and why BCrypt

The Dao in DaoAuthenticationProvider stands for Data Access Object — it's the provider that reaches into a datastore for user records. And BCryptPasswordEncoder is preferred because it generates a fresh random salt for every password, so two identical passwords hash to different values and precomputed-hash attacks fail.

2.5How one request flows through it#

Put the cast in motion. When a request arrives, this is the sequence, and it is a favorite thing to be asked to narrate.

  1. The Security Filter Chain intercepts the request first.
  2. If the user isn't authenticated yet, the auth filters trigger the Authentication Manager.
  3. The Manager hands the credentials to a configured Authentication Provider.
  4. The Provider uses the UserDetailsService to load the user from the database, and the PasswordEncoder to compare the submitted password against the stored hash.
  5. The result is returned to the user as success or a 401 Unauthorized.
  6. On success, the Authentication object is stored in the SecurityContextHolder — the context now represents the logged-in user for the rest of the request.
request Security Filter Chain filter filter filter … Authentication Manager Authentication Provider UserDetailsService PasswordEncoder DB SecurityContext Holder ✓ → Controller
Request → filter chain → manager → provider → (UserDetailsService + PasswordEncoder → DB) → context → controller.

2.6Setting up basic authentication#

Basic authentication is the simplest scheme: the client sends its username and password with every request, and the server checks them each time. Spring Security gives you this almost for free. On start.spring.io you generate a project with two dependencies — Spring Web and Spring Security — and the moment the security dependency is on the classpath, something surprising happens.

⭐ TRICK · secured by default, so you opt out

Adding spring-boot-starter-security locks down every endpoint automatically. There is no "turn on security" step — it's already on. Spring even creates a default user named user and prints a one-time generated password to the terminal on startup. You then relax rules with permitAll() rather than adding protection endpoint by endpoint. This is the mental flip most beginners miss.

Write a trivial endpoint, run the app with ./gradlew bootRun, and visit it — you're bounced to a login page. Enter user and the terminal password, and the resource loads.

TestController.java
@RestController
@RequestMapping("/api/v1")
public class TestController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello from authenticated endpoint!";
    }
}

To move past the generated user and take control of the rules, you define your own Security Filter Chain bean. This one permits sign-up freely, requires a login everywhere else, and turns on HTTP Basic:

SecurityConfig.java — the filter chain
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.disable()).authorizeHttpRequests(auth -> auth.requestMatchers("/signup/**").permitAll()   // open
        .requestMatchers("/users/**").authenticated() // login required
        .anyRequest().authenticated()).httpBasic(Customizer.withDefaults());            // Basic Auth
        return http.build();
    }
}

The provider needs to know where users come from and how their passwords are hashed. That is the job of a UserDetailsService plus a PasswordEncoder, wired into a DaoAuthenticationProvider:

UserDetailsServiceImpl.java
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

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

    @Override
    public UserDetails loadUserByUsername(String email) {
        User user = userRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("Not found: " + email));

        return org.springframework.security.core.userdetails.User.withUsername(user.getEmail()).password(user.getPassword())     // already BCrypt-hashed in DB
        .roles(user.getRole().name())     // e.g. "USER" or "ADMIN"
        .build();
    }
}
SecurityConfig.java — encoder + provider
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();   // salts each password
}

@Bean
public AuthenticationProvider authenticationProvider(UserDetailsService uds) {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(uds);
    provider.setPasswordEncoder(passwordEncoder());
    return provider;
}

Anywhere inside a request you can ask who the current user is by reading the Principal out of the SecurityContextHolder:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();

What "Basic Auth" looks like on the wire#

Basic Auth is not magic: the client encodes username:password in base64 and puts it in an Authorization header on every request. The server decodes it, runs the flow above, and answers.

Client Server Authorization: Basic base64(user:pass) 200 OK · or 401 / 403
Credentials ride along on every request — base64-encoded, not encrypted.
⚠ PITFALL · base64 is not encryption

base64 is trivially reversible — anyone who reads the header reads the password. Basic Auth is only safe over HTTPS/TLS, where the whole channel is encrypted. Sending Basic Auth over plain HTTP is the MitM scenario from section 2.1, handed to the attacker.

2.7Role-based access control#

Role-based access control (RBAC) gives each user a role, and each role a fixed set of permissions — so access is decided by role, not by hand for every person. This is authorization made manageable. Instead of "can Abdul see reports? can Priya?", you say "Admins see reports," and assign Abdul the Admin role.

Analogy · recall

A company with three roles: an Admin adds/removes employees, sees every report, changes settings; a Manager views reports and adds employees but can't touch settings; an Employee only sees their own tasks. Same building, three keycards, three sets of doors that open.

RBAC matters for APIs on four counts: security (users reach only what they need), simpler management (assign a role, not a pile of individual permissions), consistency (everyone in a role gets the same access — no more, no less), and compliance (regulators want provable control over who touches sensitive data).

USER ADMIN GET / GET /admins ✓ 200 ✕ 403 ✓ 200
Role decides the door: USER is blocked from /admins with 403; ADMIN reaches both.
⚠ PITFALL · the ROLE_ prefix trap [+]

Spring stores roles internally with a ROLE_ prefix (an ADMIN role becomes authority ROLE_ADMIN). The helper hasRole("ADMIN") adds that prefix for you. If you write hasRole("ROLE_ADMIN") it checks for ROLE_ROLE_ADMIN, and a genuine admin gets 403 Forbidden. Rule: pass the bare role to hasRole, or the full authority to hasAuthority("ROLE_ADMIN"). [+] not in the slides; a standard Spring gotcha.

2.8Testing it with Postman#

With the app running (./gradlew bootRun), you register two users through the API — one plain USER and one ADMIN — by POSTing JSON to the register endpoint:

POST /register — the admin body
{
  "name": "Abdul Rahman",
  "email": "abdulr@criodo.com",
  "password": "strongpassword",
  "role": "ADMIN"
}

Then, in Postman's Authorization tab, choose type Basic Auth and fill in a registered user's email and password. Postman attaches the Authorization header to every request you send. Now probe the endpoints and watch the status codes tell the authN/authZ story:

CredentialsRequestResultWhy
valid USERGET /200 OKauthenticated + allowed
valid USERGET /admins403 Forbiddenknown, but role not permitted
valid ADMINGET /admins200 OKrole permits it
wrong passwordany401 Unauthorizedidentity not proven
💡 TIP · 401 vs 403 is the whole lesson in two numbers

401 Unauthorized means authentication failed — the server doesn't know who you are. 403 Forbidden means authentication succeeded but authorization failed — it knows you and you're still not allowed. If a valid login returns 403, look at roles, not passwords.

2.9Interview & practice Q&A#

Q1What is the difference between authentication and authorization?

Authentication verifies who you are by checking credentials; authorization decides what you're allowed to do based on your privileges. Authentication always runs first — you can't evaluate permissions for an unknown user. A failed authentication returns 401; a failed authorization returns 403.

Q2Where does Spring Security sit relative to your controllers?

It inserts a chain of filters before the DispatcherServlet, which is the box that routes requests to controllers. So every request is authenticated and authorized before your controller code runs. Security is enforced at the request layer, not inside business logic.

Q3Walk through how a request is authenticated.

The Security Filter Chain intercepts it; if the user isn't authenticated, the auth filters call the Authentication Manager; the Manager delegates to an Authentication Provider; the Provider loads the user via UserDetailsService and compares passwords via the PasswordEncoder; on success the Authentication object is stored in the SecurityContextHolder, otherwise a 401 is returned.

Q4Why use BCryptPasswordEncoder instead of storing passwords directly?

Storing plaintext (what NoOpPasswordEncoder does) means a database leak instantly exposes every password. BCrypt hashes passwords one-way and generates a random salt per password, so identical passwords produce different hashes and precomputed-hash (rainbow-table) attacks don't work. You compare by re-hashing the input, never by decrypting.

Q5What does the UserDetailsService do, and what's its one method?

It's the interface Spring Security uses to load a user's details during authentication. Its method is loadUserByUsername(), which you implement to fetch the user (username, hashed password, roles) from your database, usually through a repository, and return a UserDetails object.

Q6Is Basic Auth secure on its own?

No. Basic Auth base64-encodes username:password into the Authorization header, and base64 is reversible, not encrypted. It's only safe over HTTPS/TLS, which encrypts the entire connection. Over plain HTTP the credentials are effectively sent in the clear.

Q7A user logs in correctly but gets 403 on an endpoint. What's wrong?

Authentication succeeded (or they'd see 401), so it's an authorization problem — their role doesn't have permission for that endpoint. Check the role rules on the route. A common cause is the ROLE_ prefix mistake: hasRole("ROLE_ADMIN") actually checks for ROLE_ROLE_ADMIN and blocks real admins.

Q8Why is RBAC preferred over per-user permissions?

It's simpler to manage, consistent, and auditable. You define permissions once per role and assign roles to users, instead of maintaining a permission list per individual. Everyone in a role gets exactly the same access, which also makes compliance and reviews far easier.

Q9What happens the instant you add the Spring Security dependency?

Every endpoint becomes secured by default. Spring auto-creates a user named user with a random password printed to the console at startup, and unauthenticated requests are redirected to a login. You then relax rules (e.g. permitAll() on public routes) rather than adding protection piece by piece.

2.10Key takeaways#

  • An unsecured endpoint is an open door — the Panera leak was missing checks, not a sophisticated attack.
  • Authentication (who are you? → 401 on fail) always precedes authorization (what may you do? → 403 on fail).
  • Spring Security runs as a filter chain in front of the DispatcherServlet, so it guards requests before controllers.
  • The cast: Filter Chain → Authentication Manager → Authentication Provider → UserDetailsService + PasswordEncoder → SecurityContextHolder.
  • Adding the dependency secures everything by default; you opt out with permitAll().
  • Hash passwords with BCrypt (salted); NoOp stores plaintext and must never ship.
  • Basic Auth = base64 credentials on every request — safe only over HTTPS.
  • RBAC gates endpoints by role; mind the ROLE_ prefix when using hasRole.
authN = who authZ = what 401 vs 403 filter chain first DispatcherServlet Auth Manager → Provider UserDetailsService BCrypt salts secured by default permitAll to open Basic = base64 HTTPS or bust RBAC by role ROLE_ prefix trap

3 · MicroexercisesNow do it yourself#

Reading proves you recognize the pattern. These prove you can produce it. Attempt each before opening the walkthrough — both are runnable in your head or a scratch file.

3.1Build it — a Security Filter Chain#

// your task

Write a SecurityFilterChain bean for a Spring Boot 3 / Spring Security 6 app that:

  • permits all requests to /signup/** without a login;
  • requires authentication for /users/** and for every other request;
  • uses HTTP Basic authentication.

Expected result: anonymous GET /signup/new → 200; anonymous GET /users/1 → 401; the same request with valid Basic Auth credentials → 200.

Show the full walkthrough

Step 1 — start from the bean signature. In modern Spring Security you don't extend a base class; you expose a SecurityFilterChain bean that receives an HttpSecurity builder and returns http.build(). Annotate the config class with @Configuration and @EnableWebSecurity.

Step 2 — declare the rules most-specific first. Inside authorizeHttpRequests, order matters: the first matcher that matches a request wins. Put /signup/** (permitAll) and /users/** (authenticated) before the catch-all anyRequest().

Step 3 — turn on the scheme. Add httpBasic(Customizer.withDefaults()). For a stateless API demo you also disable CSRF; leave it on for browser-form apps.

SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

Why: the filter chain is where authorization rules live — declaring them here means every request is judged before it reaches a controller, which is the whole point of section 2.5.

3.2Diagnose it — the admin who can't get in#

// your task

A teammate reports that a user with role ADMIN, sending correct credentials, gets 403 Forbidden on GET /admins. Their UserDetailsService builds users with .roles("ADMIN"), and passwords verify fine (plain GET / works). Predict the exact failure, name the root cause, and fix the config below.

SecurityConfig.java — buggy
.authorizeHttpRequests(auth -> auth
    .requestMatchers("/admins").hasRole("ROLE_ADMIN")
    .anyRequest().authenticated())

Expected result after the fix: ADMIN → /admins returns 200; USER → /admins still returns 403.

Show the full walkthrough

Predict the failure first. The admin authenticates successfully — that's why they see 403 and not 401 — but the authorization check never matches, so access is denied on /admins for everyone, admins included.

Root cause — a doubled prefix. Spring stores a role internally as an authority with a ROLE_ prefix, so .roles("ADMIN") becomes the authority ROLE_ADMIN. The convenience method hasRole("X") prepends ROLE_ for you. Passing hasRole("ROLE_ADMIN") therefore tells Spring to look for the authority ROLE_ROLE_ADMIN — which no user ever has. The rule can't match, so the admin is forbidden.

Step 1 — pick one convention. Either give hasRole the bare role name and let it add the prefix, or switch to hasAuthority and give the full authority string. Don't mix them.

SecurityConfig.java — fixed (either line works)
.authorizeHttpRequests(auth -> auth
    .requestMatchers("/admins").hasRole("ADMIN")          // adds ROLE_ for you
    // .requestMatchers("/admins").hasAuthority("ROLE_ADMIN")  // equivalent
    .anyRequest().authenticated())

Why: RBAC only works if the string you check equals the authority the user actually holds. The ROLE_ prefix is applied in exactly one place depending on which helper you call — knowing which one saves you from chasing a phantom "permissions bug" that is really a typo.

bean returns http.build() specific matchers first httpBasic(withDefaults()) hasRole adds ROLE_ 403 = authZ, not authN roles(...) vs hasRole(...)