- Chapter
- Chapter 1 — Spring Security Fundamentals
- Prerequisites
- Volume IV (Spring MVC request lifecycle)
- You will be able to
- Explain Spring Security's core abstractions, distinguish authentication from authorisation, describe how principals and authorities are represented, and understand Boot's secure defaults
- Covers
- Authentication · Authorization · Principal · SecurityContext
On this page
- 1. Concept / Definition
- 2. Why It Exists
- 3. Core Concepts
- 4. How It Works Internally
- 4A. Deep Dive — Spring Boot's Secure Defaults and Explicit Configuration
- 5. APIs / Features / Syntax
- 6. Practical Examples
- 7. Common Problems & Mistakes
- 8. Performance & Production Considerations
- 9. Architecture & Design Considerations
- 10. Interview Questions & Answers
1Concept / Definition
Spring Security's architecture answers two questions for every protected operation: who is the caller? (authentication) and may the caller do this? (authorisation). The result of authentication — a principal with its authorities — is stored in the SecurityContext, which authorisation decisions read.

| Concept | Spring Security type | Role |
|---|---|---|
| Authentication | Authentication, AuthenticationManager | Verify credentials → authenticated Authentication |
| Authorization | AuthorizationManager<T> | Grant or deny access to a request, method or object |
| Principal | Authentication.getPrincipal() (e.g. UserDetails, Jwt, OidcUser) | The identity |
| Authorities | Collection<? extends GrantedAuthority> | Roles (ROLE_ADMIN), permissions, scopes (SCOPE_read) |
| SecurityContext | SecurityContext, SecurityContextHolder | Holds the current Authentication for the request |
2Why It Exists
Security logic scattered across controllers is inconsistent and easy to bypass. Spring Security (originally Acegi, 2004) centralised it in a framework: authentication mechanisms are pluggable providers, authorisation is declarative, and both run before application code in servlet filters and method interceptors. The result is defence that applies uniformly — including to endpoints developers forget — with secure defaults and hooks for every major protocol (form login, HTTP Basic, OAuth2, OIDC, SAML, LDAP, X.509, WebAuthn).
3Core Concepts
3.1 Authentication

public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials(); // password, token — erased after authentication
Object getDetails(); // IP address, session id, …
Object getPrincipal(); // UserDetails, Jwt, OidcUser, String
boolean isAuthenticated();
}| Mechanism | Authentication type |
|---|---|
| Form login / HTTP Basic | UsernamePasswordAuthenticationToken |
| Bearer JWT (resource server) | JwtAuthenticationToken |
| OAuth2/OIDC login | OAuth2AuthenticationToken |
| Anonymous | AnonymousAuthenticationToken |
| Remember-me, X.509, pre-authenticated | Specific token types |
3.2 Authorization
Authorisation happens at several levels, all built on AuthorizationManager:
| Level | Configuration |
|---|---|
| HTTP requests | http.authorizeHttpRequests(…) |
| Methods | @EnableMethodSecurity + @PreAuthorize etc. |
| Objects/data | @PostAuthorize, @AuthorizeReturnObject, domain checks |
| Messages | WebSocket/messaging security |
3.3 Principal
The principal is whatever represents the identity for the mechanism used: a UserDetails from a user store, a Jwt with claims, an OidcUser with ID token claims, or simply a username string. Authentication.getName() gives a uniform identifier (username, sub claim).
3.4 SecurityContext

The SecurityContextHolder stores the SecurityContext for the current execution (thread-local by default in servlet apps). Any code in the request — filters, controllers, services, @PreAuthorize expressions — can read the current Authentication from it (Topic 1.3).
4How It Works Internally
1. Boot registers a DelegatingFilterProxy named springSecurityFilterChain in the servlet container; it delegates to the FilterChainProxy bean.
2. FilterChainProxy selects the first SecurityFilterChain matching the request and runs its filters (Topic 1.2).
3. An authentication filter extracts credentials, calls the AuthenticationManager, and on success stores the resulting Authentication in a new SecurityContext via the SecurityContextHolderStrategy (and optionally persists it via SecurityContextRepository).
4. The AuthorizationFilter asks the configured AuthorizationManager whether the current Authentication may access the request.
5. Failures raise AuthenticationException (→ 401 via entry point) or AccessDeniedException (→ 403 via access-denied handler), handled by ExceptionTranslationFilter.
6. At the end of the request, the context is cleared from the thread.
4ADeep Dive — Spring Boot's Secure Defaults and Explicit Configuration

@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/public/**", "/actuator/health/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.logout(Customizer.withDefaults());
return http.build();
}
@Bean
UserDetailsService users(PasswordEncoder encoder) { // demo only
return new InMemoryUserDetailsManager(
User.withUsername("admin")
.password(encoder.encode("change-me"))
.roles("ADMIN")
.build());
}
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}| Boot default | When it applies | How to replace |
|---|---|---|
Default SecurityFilterChain (all authenticated, form + Basic) | No SecurityFilterChain bean defined | Declare your own bean |
Generated user password | No UserDetailsService, AuthenticationProvider or OAuth2 config | Define a user store or OAuth2 resource server |
| CSRF protection | Always, unless disabled | Configure per chain (Volume VI Ch. 7) |
| Security headers | Always | http.headers(...) |
5APIs / Features / Syntax
| API | Purpose |
|---|---|
spring-boot-starter-security | Spring Security + Boot auto-configuration |
SecurityFilterChain bean + HttpSecurity lambda DSL | Web security configuration |
Authentication, GrantedAuthority, SimpleGrantedAuthority | Identity representation |
AuthenticationManager, AuthenticationProvider | Authentication |
AuthorizationManager, authorizeHttpRequests, @EnableMethodSecurity | Authorisation |
SecurityContextHolder, SecurityContextHolderStrategy | Context access |
logging.level.org.springframework.security | Diagnostics |
6Practical Examples
6.1 Seeing the architecture in logs
TRACE FilterChainProxy : Trying to match request against
DefaultSecurityFilterChain [...] (1/1)
DEBUG FilterChainProxy : Securing GET /admin/reports
TRACE AuthorizationFilter : Authorizing GET /admin/reports
DEBUG ExceptionTranslationFilter : Sending AnonymousAuthenticationToken
to authentication entry point since access is denied6.2 Reading the current user in a controller
@GetMapping("/me")
Map<String, Object> me(Authentication authentication) {
return Map.of("name", authentication.getName(),
"authorities",
AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}6.3 Testing a rule
@WebMvcTest(AdminController.class)
@Import(SecurityConfig.class)
class AdminSecurityTest {
@Autowired MockMvcTester mvc;
@Test
void anonymousIsRedirectedOrUnauthorized() {
assertThat(mvc.get().uri("/admin/reports")).hasStatus3xxRedirection();
}
@Test
@WithMockUser(roles = "USER")
void userIsForbidden() {
assertThat(mvc.get().uri("/admin/reports")).hasStatus(HttpStatus.FORBIDDEN);
}
@Test
@WithMockUser(roles = "ADMIN")
void adminIsAllowed() {
assertThat(mvc.get().uri("/admin/reports")).hasStatusOk();
}
}6.4 401 vs 403
| Situation | Status | Meaning |
|---|---|---|
| No or invalid credentials | 401 Unauthorized | Authenticate (again) |
| Valid credentials, insufficient rights | 403 Forbidden | Authenticated but not allowed |
| Browser app, unauthenticated | 302 to login page | Entry point chooses redirect |
6.5 Security concerns and their Spring Security components
| Concern | Component |
|---|---|
| Credential verification | AuthenticationProviders |
| Identity storage per request | SecurityContextHolder |
| Session persistence of identity | SecurityContextRepository |
| Access decisions | AuthorizationManager |
| Error responses | AuthenticationEntryPoint, AccessDeniedHandler |
| Browser protections | CSRF, headers, CORS filters |
7Common Problems & Mistakes
- Mixing authentication and authorisation logic in controllers.
- Relying on Boot's generated user beyond local development.
- Returning 403 for unauthenticated calls (or 401 for forbidden ones) — confuses clients.
- Checking roles by string comparison in business code instead of using authorities and method security.
- Disabling security for "internal" endpoints that are reachable externally.
- Logging credentials or tokens in debug output.
8Performance & Production Considerations
| Concern | Guidance |
|---|---|
| Filter overhead | Negligible except password hashing (by design) and remote token introspection |
| Auditing | Publish and record authentication success/failure events |
| Monitoring | Rates of 401/403, login failures, lockouts |
| Secrets | Credentials and signing keys from secret stores (Volume III) |
9Architecture & Design Considerations
- Centralised, declarative security — never ad-hoc checks in controllers.
- Separation of identity (authentication) from access (authorisation) across components and teams.
- Authorities as the contract between identity sources and application rules.
- Security as a platform starter with standard chains, error formats and audit events.
10Interview Questions & Answers
What is the difference between authentication and authorisation?
Authentication answers "who are you?": it verifies the caller's identity, for example by checking a password, validating a JWT issued by an identity provider or verifying a client certificate. Authorisation answers "what may you do?": it decides whether that authenticated identity is allowed to perform a specific action on a specific resource, such as approving a refund over a certain amount. Authentication normally happens once per request or session at the boundary; authorisation can happen many times, at URL level and on individual methods. A system can authenticate perfectly and still be insecure if its authorisation rules are missing or wrong.
What does the Authentication object contain?
Spring Security's Authentication object represents a caller. It contains the principal, the identity, such as a UserDetails object, a Jwt or an OidcUser; the credentials, such as a password, which are usually erased after successful login so they do not linger in memory; the granted authorities, which are the roles and permissions used for authorisation decisions; optional details, such as the remote address and session ID; and a flag indicating whether it has been authenticated. After login it is stored in the SecurityContext, which is available to the rest of the request through SecurityContextHolder or method parameters.
What is a principal?
The principal is the authenticated identity of the caller, the thing that was proven during authentication. Its type depends on how the user logged in: a UserDetails object for username and password login, a Jwt for a resource server receiving bearer tokens, an OidcUser for OpenID Connect login, or a custom type. Code can access it through Authentication.getPrincipal(), or more conveniently with @AuthenticationPrincipal in a controller method. The principal typically provides the user's identifier and attributes such as name or email, which are used for authorisation decisions, auditing and personalising responses.
What does Spring Boot configure when you add the security starter?
Adding spring-boot-starter-security secures the application immediately. Boot configures a default security filter chain that requires authentication for every request, enables form login and HTTP Basic, and, if no other user source is configured, creates a single user named user with a random password printed in the log. It enables CSRF protection for state-changing requests, adds security headers such as X-Content-Type-Options and cache-control headers, and configures session management. These defaults are deliberately strict, so nothing is accidentally exposed, but almost every real application replaces them with its own SecurityFilterChain bean.
What HTTP statuses correspond to authentication and authorisation failures?
A 401 Unauthorized response means the request lacks valid authentication: there are no credentials, the token has expired or the signature is invalid. Despite its name, it is about authentication, and it should include a WWW-Authenticate header telling the client how to authenticate. A 403 Forbidden response means the caller is authenticated, but is not allowed to perform this action, for example a customer trying to access an admin endpoint. In Spring Security, the AuthenticationEntryPoint produces the 401 and the AccessDeniedHandler produces the 403. Using them correctly helps clients decide whether to log in again or give up.
What is AuthorizationManager?
AuthorizationManager is Spring Security's single abstraction for access decisions, introduced to replace the older AccessDecisionManager and voters. Given a supplier of the current Authentication and the object being secured, such as an HTTP request, a method invocation or a message, it returns an authorisation decision. The same model is used for URL rules configured with authorizeHttpRequests, for method security annotations such as @PreAuthorize, and for messaging security. Teams can write custom implementations, for example to check ownership of a resource or call an external policy engine, and reuse them consistently across all three levels.
Why should security be enforced in filters rather than controllers?
Spring Security's filters run in the servlet filter chain before any controller code, for every request that reaches the application. Enforcing security there means it is applied centrally and consistently: developers cannot forget to add a check to a new endpoint, and requests that fail authentication are rejected before they touch application code. URL-based rules in the filter chain give a secure default, such as denying anything not explicitly allowed. Method-level annotations on services then add fine-grained authorisation. Checks scattered inside controller methods are easy to miss, hard to audit and do not protect non-HTTP entry points.
How would you organise security responsibilities across an enterprise?
Split responsibilities by what each layer knows. Centralise authentication in an identity provider, using single sign-on with OpenID Connect for users and OAuth 2.0 client credentials or workload identity for services, so applications never handle passwords. Keep authorisation rules close to the domain services that own the data, because only they understand concepts such as account ownership or approval limits. Standardise security configuration through platform starters that set up token validation, headers and error responses consistently. Enforce coarse rules at the API gateway, audit authentication and authorisation decisions centrally, and review security architecture as part of every design review.