Security Basics Interview Questions & Answers
Spring Security fundamentals — the servlet filter chain, SecurityFilterChain configuration, the default login, authentication vs authorization, the SecurityContext, password encoding, CSRF, CORS, and stateless vs session security.
Spring Security is a framework that handles authentication (who are you) and authorization (what may you do) for Spring apps. In Boot you add the starter and it auto-configures a chain of servlet filters that intercept every request before it reaches your controllers.
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-security'
Just adding the dependency locks down every endpoint behind HTTP Basic / form login with a
generated password — security is opt-out, not opt-in. You then declare a SecurityFilterChain
bean to shape the rules.
Rule of thumb: Adding the starter secures everything by default; you relax and customize from there rather than adding security piece by piece.
Spring Security is a single servlet Filter (DelegatingFilterProxy → FilterChainProxy) that
delegates to an ordered list of filters, the SecurityFilterChain. Each filter does one job —
CSRF, authentication, authorization — and passes the request along.
// request → ... → UsernamePasswordAuthenticationFilter → ... →
// ExceptionTranslationFilter → AuthorizationFilter → DispatcherServlet
A request flows through every filter; if authentication or authorization fails, a filter short-circuits the chain and writes a 401/403 instead of reaching your controller. The whole model is "a request runs a gauntlet of filters before it ever hits MVC."
Rule of thumb: Spring Security is filters all the way down — understanding the chain explains why a request is rejected before any controller code runs.
Authentication establishes identity — verifying you are who you claim (username/password, token, certificate). Authorization decides permissions — whether that identity may access a resource or perform an action.
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN") // authorization
.anyRequest().authenticated()) // authentication required
.httpBasic(withDefaults()); // how you authenticate
Authentication always comes first (a 401 means "log in"); authorization comes after (a 403 means "you're known but not allowed"). They map to two different HTTP status codes for exactly this reason.
Rule of thumb: Authentication = who you are (401 if missing); authorization = what you may do (403 if denied).
Since Spring Security 5.7 / Boot 2.7 you define a SecurityFilterChain bean (the
WebSecurityConfigurerAdapter class is removed). You receive an HttpSecurity builder, configure
it, and return build().
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/public/**").permitAll()
.anyRequest().authenticated())
.formLogin(withDefaults());
return http.build();
}
}
This component-based style lets you register several chains (each with a securityMatcher) and
keeps config testable as ordinary beans.
Rule of thumb: Configure security by returning a SecurityFilterChain bean from an
HttpSecurity builder — the adapter base class is gone.
With only the starter on the classpath and no UserDetailsService, Boot auto-creates a single
in-memory user named user with a random UUID password printed to the console at startup.
Using generated security password: 3f9a1c8e-...-b21
You override it with properties or, more realistically, by defining your own user store:
// application.yml
spring.security.user.name: admin
spring.security.user.password: secret
The generated password is a dev convenience, regenerated each restart — never a production mechanism.
Rule of thumb: The console password is scaffolding to prove security is on; replace it with a
real UserDetailsService before doing anything serious.
The SecurityContext holds the current Authentication — the authenticated principal and its
authorities. It's stored in the SecurityContextHolder, which by default uses a ThreadLocal so
any code in the request thread can reach the current user.
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
var authorities = auth.getAuthorities(); // roles/permissions
Because it's thread-bound, the context is not automatically visible in @Async methods or new
threads. In a controller, prefer injecting @AuthenticationPrincipal or an Authentication
parameter over reaching into the holder directly.
Rule of thumb: The current user lives in a thread-local SecurityContext; read it via method
parameters in controllers, and remember it doesn't cross thread boundaries.
Passwords must be stored as a salted, slow hash, never in plaintext or with a fast hash like
MD5/SHA-256. Spring Security requires a PasswordEncoder; the recommended default is BCrypt
(adaptive, salted, deliberately slow).
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // or PasswordEncoderFactories.createDelegatingPasswordEncoder()
}
// encode on signup: encoder.encode(rawPassword)
// verify on login: encoder.matches(rawPassword, storedHash)
A DelegatingPasswordEncoder prefixes the hash with the algorithm id ({bcrypt}…) so you can
migrate algorithms over time. If you store a raw password Spring will throw
IllegalArgumentException: There is no PasswordEncoder mapped for the id "null".
Rule of thumb: Always hash with BCrypt (or Argon2/scrypt) via a PasswordEncoder; never store or
compare plaintext.
CSRF (Cross-Site Request Forgery) tricks a logged-in user's browser into submitting an unwanted
state-changing request using its session cookie. Spring Security enables CSRF protection by default,
requiring a token on POST/PUT/DELETE for cookie/session-based apps.
// Stateless API authenticated by a bearer token? CSRF doesn't apply:
http.csrf(csrf -> csrf.disable()) // safe ONLY because no auth cookie is used
.sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS));
CSRF only matters when the browser automatically attaches credentials (a cookie). A token-based
API where the client must explicitly send an Authorization header isn't vulnerable, so disabling
CSRF there is correct. For server-rendered, cookie-session apps, keep it on.
Rule of thumb: Keep CSRF for cookie/session web apps; disable it for stateless token APIs — never disable it just to make a failing form "work."
CORS (Cross-Origin Resource Sharing) controls which browser origins may call your API. It's a browser policy, not authentication, but Spring Security must be told to honor it, otherwise the security filters reject the preflight before your CORS config runs.
http.cors(withDefaults()); // delegates to a CorsConfigurationSource bean
@Bean
CorsConfigurationSource corsConfig() {
var c = new CorsConfiguration();
c.setAllowedOrigins(List.of("https://app.example.com"));
c.setAllowedMethods(List.of("GET", "POST"));
var src = new UrlBasedCorsConfigurationSource();
src.registerCorsConfiguration("/**", c);
return src;
}
Enabling http.cors() makes the chain let the preflight OPTIONS through and apply your
CorsConfigurationSource. CORS does not secure anything — it only relaxes the browser's
same-origin block.
Rule of thumb: Configure CORS via a CorsConfigurationSource and wire it with http.cors();
remember it's a browser relaxation, not a security control.
Session-based security authenticates once and stores the SecurityContext in the HTTP session
(a JSESSIONID cookie); later requests are recognized by that cookie. Stateless security carries
no server session — every request must present a credential (typically a JWT) that's validated
anew.
http.sessionManagement(sm ->
sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // no JSESSIONID stored
Stateless scales horizontally (any node can serve any request, no sticky sessions) and suits SPAs and mobile clients; session-based is simpler for server-rendered apps and supports easy server-side logout/invalidation. Stateless + token is the norm for REST APIs.
Rule of thumb: Use session-based security for server-rendered web apps; use stateless token security for REST APIs and anything that must scale across nodes.
permitAll() lets a request through without requiring authentication; authenticated() requires
a logged-in principal. When no one is logged in, Spring still assigns an anonymous
AnonymousAuthenticationToken rather than a null Authentication.
auth.requestMatchers("/login", "/css/**").permitAll()
.requestMatchers("/account/**").authenticated();
The anonymous token means getAuthentication() is never null, so authorization rules can treat
"not logged in" uniformly. permitAll still runs the filter chain — it just doesn't demand
credentials — whereas an ignoring() web-security rule skips the chain entirely (rarely what you want).
Rule of thumb: permitAll = open but still filtered; unauthenticated requests carry an anonymous
token, so the context is never null.
URL-based security matches request paths in the SecurityFilterChain. Method-level security
annotates service/controller methods and is enabled with @EnableMethodSecurity, letting you secure
business methods regardless of how they're reached.
@EnableMethodSecurity // turns on @PreAuthorize etc.
class SecurityConfig { ... }
@PreAuthorize("hasRole('ADMIN')")
void deleteUser(Long id) { ... }
URL rules are broad and centralized; method security is fine-grained and lives next to the logic,
catching access even if a new controller forgets a URL rule. Many apps use both: coarse URL rules
plus @PreAuthorize on sensitive service methods.
Rule of thumb: Use URL rules for coarse gatekeeping and method security (@PreAuthorize) for
fine-grained, defense-in-depth checks close to the logic.
You can register several SecurityFilterChain beans, each scoped by a securityMatcher, and
ordered with @Order. This lets one app apply different security models to different path groups —
e.g. stateless JWT for /api/** and form login for the admin UI.
@Bean @Order(1)
SecurityFilterChain api(HttpSecurity http) throws Exception {
http.securityMatcher("/api/**")
.sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(a -> a.anyRequest().authenticated())
.oauth2ResourceServer(o -> o.jwt(withDefaults()));
return http.build();
}
@Bean @Order(2)
SecurityFilterChain web(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(a -> a.anyRequest().authenticated())
.formLogin(withDefaults());
return http.build();
}
Spring evaluates chains in order and uses the first whose matcher matches. Without a matcher a chain matches everything, so order and matchers matter.
Rule of thumb: Use multiple ordered, securityMatcher-scoped chains when one app needs distinct
security models (e.g. token API vs. session admin) side by side.
The recurring traps:
- Disabling CSRF on a cookie-session app just to silence a 403 — opens a real vulnerability.
- Storing plaintext passwords or forgetting a
PasswordEncoder(causes the "no PasswordEncoder mapped" error). - Wrong rule order — Spring uses the first matching
requestMatchers, so a broadanyRequest()placed early shadows specific rules. permitAllon a sensitive path copied from an example.- Relying on the generated password in production.
auth.requestMatchers("/admin/**").hasRole("ADMIN") // specific FIRST
.anyRequest().authenticated(); // catch-all LAST
Rule of thumb: Order rules specific-to-general, always hash passwords, and only disable CSRF for genuinely stateless token APIs.
More Security interview questions
More ways to practice
The self-quiz is live. Join our channel for updates, new content & tech tips.