Skip to main content

Spring Security Overview

Spring Security is the standard security framework for Spring applications. Every HTTP request passes through an ordered chain of filters that authenticate the caller, enforce access rules, and protect against CSRF/CORS attacks before the request reaches any controller. Understanding the filter chain architecture, JWT validation, and OAuth2 flows is essential for building production-grade APIs.

Key Concepts at a Glance

  • Security Filter Chain: An ordered list of servlet filters — configured as a SecurityFilterChain bean — that every request passes through before reaching the application.
  • DelegatingFilterProxy: The single servlet filter registered in the container; it delegates to Spring's FilterChainProxy.
  • FilterChainProxy: The Spring Security component that holds all SecurityFilterChain beans and routes each request to the first matching chain.
  • SecurityContextHolder: ThreadLocal-backed store that holds the SecurityContext (which contains the logged-in user's Authentication) for the current request thread.
  • Authentication: Holds the principal (user identity), credentials (erased after auth), and a Collection<GrantedAuthority>.
  • UserDetailsService: Interface you implement to load a user record from your database by username.
  • BCryptPasswordEncoder: The standard PasswordEncoder for Spring Boot apps — slow, salted hash that resists brute-force and rainbow-table attacks.
  • AuthenticationManager: The entry point for authentication; delegates to one or more AuthenticationProvider implementations.
  • DaoAuthenticationProvider: The default provider for username/password auth — calls UserDetailsService + PasswordEncoder.
  • AuthorizationFilter: The final filter; evaluates the rules in authorizeHttpRequests(...). First matching rule wins.
  • @EnableMethodSecurity: Activates @PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter for method-level access control.
  • @PreAuthorize: SpEL expression evaluated before a method runs; throws AccessDeniedException if false.
  • Role vs Authority: A role is an authority with the ROLE_ prefix. hasRole('ADMIN') checks for ROLE_ADMIN; hasAuthority('read:orders') checks exact string.
  • JWT (JSON Web Token): Compact, self-contained token — three Base64URL parts (Header.Payload.Signature) — that carries user identity and claims; server validates signature without a DB lookup.
  • JwtDecoder: Spring Security component that validates a JWT's signature, exp, iss, and aud claims on every request.
  • JwtAuthenticationConverter: Maps JWT claims to Spring GrantedAuthority objects; customize when roles are in a non-standard claim.
  • OAuth2 Authorization Code Flow: The user authenticates with an external authorization server; the backend exchanges an authorization code for tokens. The token never passes through the browser URL.
  • Client Credentials Flow: Service-to-service auth — the service authenticates directly with the authorization server using client_id + client_secret.
  • OIDC (OpenID Connect): Extension of OAuth2 that adds an ID Token (a JWT with user identity claims: sub, name, email) for authenticating users via social login.
  • CSRF (Cross-Site Request Forgery): Attack where a malicious site tricks the browser into sending requests using session cookies. Mitigated by CSRF tokens. Disable for stateless JWT APIs.
  • CORS (Cross-Origin Resource Sharing): Browser policy blocking cross-origin requests. Configure via http.cors() so OPTIONS preflight is handled before authentication filters.

Quick-Reference Table

Annotation / APIPurposeKey Note
SecurityFilterChain beanDefines the security rules for a URL namespaceDeclare multiple with @Order for different namespaces
@EnableWebSecurityEnables Spring Security's web security supportOften used with @Configuration
@EnableMethodSecurityActivates @PreAuthorize etc.Replaces deprecated @EnableGlobalMethodSecurity
http.authorizeHttpRequests(...)URL-based access rules — evaluated top to bottomPut anyRequest() last
http.csrf(AbstractHttpConfigurer::disable)Disables CSRF protectionCorrect for stateless JWT REST APIs
http.cors(cors -> cors.configurationSource(...))Configures CORS at the security layerMust be here — not only in WebMvcConfigurer
http.oauth2ResourceServer(o -> o.jwt(...))Enables JWT Bearer token validationAuto-configures BearerTokenAuthenticationFilter
http.oauth2Login(...)Social login (OIDC authorization code flow)Principal is OidcUser after login
SessionCreationPolicy.STATELESSPrevents session creation/useRequired for stateless JWT APIs
@PreAuthorize("hasRole('X')")Gate method on roleWorks on public methods only (proxy limitation)
@PreAuthorize("#id == authentication.principal.id")Check method param against current userParameter binding via #paramName
@PostAuthorize("returnObject.ownerId == ...")Check returned value after method runsCosts a DB query even if check fails
@AuthenticationPrincipal Jwt jwtInject decoded JWT in controllerWorks with resource server setup
@AuthenticationPrincipal OidcUser userInject OIDC user after social loginUse .getSubject(), .getEmail()
@WithMockUser(roles = "ADMIN")Mock auth in Spring Security testsNo real auth required
BCryptPasswordEncoderHashes and verifies passwordsUse cost factor ≥ 10
CookieCsrfTokenRepository.withHttpOnlyFalse()CSRF token in readable cookie for SPAsAllows JS to read and include token in header
JwtGrantedAuthoritiesConverterMaps JWT claims to authoritiesSet authoritiesClaimName for custom claim

Learning Path

Suggested reading order for a returning Java developer:

  1. Security Filter Chain — start here; the mental model of how filters compose explains all Spring Security behaviour.
  2. AuthenticationUserDetailsService + BCryptPasswordEncoder + AuthenticationManager; the foundation for all login flows.
  3. Authorization — URL rules vs. @PreAuthorize; the role/authority distinction; SpEL expressions.
  4. CSRF & CORS — fix the most common 403/401 config errors; know when to disable CSRF.
  5. JWT — token structure, signing algorithms, NimbusJwtDecoder, claims-to-authorities mapping.
  6. OAuth2 & OIDC — Authorization Code, Client Credentials, PKCE; configure Spring Boot as client and resource server.

Notes in This Domain

NoteDescription
Security Filter ChainDelegatingFilterProxy, FilterChainProxy, SecurityContextHolder, custom filter registration.
AuthenticationUserDetailsService, BCryptPasswordEncoder, AuthenticationManager, custom login endpoint.
AuthorizationURL rules, @PreAuthorize, SpEL expressions, PermissionEvaluator.
JWTToken structure, symmetric vs. asymmetric signing, Spring resource server validation, refresh tokens.
OAuth2 & OIDCAuthorization Code, Client Credentials, PKCE, social login, resource server.
CSRF & CORSCSRF token protection, when to disable, CORS preflight, CorsConfigurationSource.

Top 5 Interview Questions

Q1: What is the Spring Security filter chain and how does it work? A: Spring Security registers a FilterChainProxy bean that holds all SecurityFilterChain beans. Every HTTP request is intercepted by DelegatingFilterProxy (a standard servlet filter), which delegates to FilterChainProxy. It routes the request to the first SecurityFilterChain whose requestMatcher matches, then executes each filter in order: load user (SecurityContextHolderFilter), validate token/credentials, check CSRF, enforce access rules (AuthorizationFilter). Only after all filters pass does the request reach the controller.

Q2: How do you implement stateless JWT authentication in Spring Boot? A: Add spring-boot-starter-oauth2-resource-server. Configure spring.security.oauth2.resourceserver.jwt.jwk-set-uri (or secret-key). In your SecurityFilterChain: set SessionCreationPolicy.STATELESS, disable CSRF (csrf.disable()), add .oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults())). Spring Security adds BearerTokenAuthenticationFilter automatically — it extracts Authorization: Bearer <token>, validates via JwtDecoder, converts claims to authorities, and stores JwtAuthenticationToken in SecurityContextHolder. Use @AuthenticationPrincipal Jwt jwt in controllers to access claims.

Q3: What is the difference between hasRole('ADMIN') and hasAuthority('read:orders')? A: Both check the user's GrantedAuthority collection. hasRole('ADMIN') automatically prefixes the argument with ROLE_ and checks for ROLE_ADMIN. hasAuthority('read:orders') checks for the exact string read:orders with no prefix. The convention: use hasRole for coarse-grained roles (stored with ROLE_ prefix), use hasAuthority for fine-grained permissions (OAuth2 scopes, permission strings like write:products). Mixing them incorrectly is a common source of inexplicable 403 errors.

Q4: Why do preflight OPTIONS requests fail with 401 in Spring Security and how do you fix it? A: The browser sends an OPTIONS preflight request without an Authorization header to ask permission. Spring Security's BearerTokenAuthenticationFilter runs first, finds no token, and returns 401 — the actual request is never attempted. Fix: configure CORS via http.cors(cors -> cors.configurationSource(...)) — Spring Security processes CORS headers before authentication for OPTIONS requests. Alternatively, add .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() to your authorization rules.

Q5: What is the difference between the OAuth2 Authorization Code flow and the Client Credentials flow? A: Authorization Code involves a human user: they log in to the authorization server, grant consent, and the client receives a token scoped to their permissions. It is used by web and mobile apps. Client Credentials has no user: the service authenticates directly with the auth server using its own client_id + client_secret and receives a token representing the service. It is used for machine-to-machine API calls. In Spring Boot, configure Client Credentials with authorization-grant-type: client_credentials and use an OAuth2-aware WebClient to auto-attach tokens.


Further Reading