Skip to content

Security Basics Interview Questions & Answers

15 questions Updated 2026-06-26 Share:

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.

Read the in-depth guideSpring Security Basics: The Filter Chain, Explained(opens in new tab)
15 of 15

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.

An authority is any granted permission string. A role is just an authority by convention prefixed with ROLE_. The hasRole("ADMIN") helper adds the prefix for you; hasAuthority uses the raw string.

.requestMatchers("/admin/**").hasRole("ADMIN")          // matches authority "ROLE_ADMIN"
.requestMatchers("/reports/**").hasAuthority("REPORT_READ")

So a user granted ROLE_ADMIN passes hasRole("ADMIN"). Roles are coarse groupings ("ADMIN"), fine-grained authorities model specific permissions ("REPORT_READ"). Mixing them is fine; just keep the ROLE_ prefix convention straight.

Rule of thumb: Roles are authorities with a ROLE_ prefix — use hasRole for the prefix and hasAuthority for the literal string.

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 broad anyRequest() placed early shadows specific rules.
  • permitAll on 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 ways to practice

The self-quiz is live. Join our channel for updates, new content & tech tips.

Join our WhatsApp Channel