skip to content

Security HTTP Headers

Spring Security sets HSTS, nosniff, frame options and cache headers by default, and lets you add CSP and referrer policy. Interviewers ask what each header actually prevents, because reciting names is not the same as knowing.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

Which HTTP security response headers does Spring Security add by default, and where does that behavior come from?

level: juniorimportance: must knowfreq 70%

answer

  1. HeadersConfigurer = secure defaults
  2. nosniff + DENY + no-store + HSTS(https)
  3. CSP & Referrer-Policy opt-in only
  4. X-XSS-Protection now '0'
  5. http.headers(...) DSL

basics

~10 s

By default Spring Security adds protective response headers automatically: Cache-Control (no-store), X-Content-Type-Options: nosniff, X-Frame-Options: DENY, and Strict-Transport-Security (HSTS) on HTTPS requests. You don't configure anything to get them.

solid answer

~40 s

Out of the box, Spring Security applies a secure-by-default set of response headers via HeadersConfigurer (configured through http.headers(...)). The defaults are: Cache-Control/Pragma/Expires that disable caching, X-Content-Type-Options: nosniff (stops MIME sniffing), X-Frame-Options: DENY (anti-clickjacking), and Strict-Transport-Security (HSTS) added only on secure/HTTPS requests. In Spring Security 6 the X-XSS-Protection header is written as '0' (disabled) per OWASP guidance since the legacy filter caused vulnerabilities. Notably, Content-Security-Policy and Referrer-Policy are NOT sent by default — you opt in explicitly. Each writer can be tuned or disabled individually inside the headers DSL. These headers ship the moment Spring Security is on the classpath with a filter chain, so they're a free baseline of browser-side hardening you inherit without writing code.

code

java · 16 lines
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // These defaults are already ON without this block; shown for clarity.
        http.headers(headers -> headers
            .contentTypeOptions(withDefaults())     // X-Content-Type-Options: nosniff
            .frameOptions(withDefaults())           // X-Frame-Options: DENY
            .cacheControl(withDefaults())            // no-cache/no-store/...
            .httpStrictTransportSecurity(withDefaults()) // HSTS on HTTPS
        );
        return http.build();
    }
}

go deeper

for a junior

Know the four default headers by name and that they're free/automatic.

for a middle

Explain each header's purpose and that CSP/Referrer-Policy are opt-in.

for a senior

Discuss the HeadersConfigurer/HeaderWriter mechanism and the X-XSS-Protection=0 change.

for a principal

Reason about proxy/forwarded-headers effects on HSTS and static-asset caching trade-offs.

## What are HTTP security headers? HTTP response headers are metadata the server sends alongside a page. A handful of them instruct the *browser* to enforce security rules the server cannot enforce alone — blocking framing, MIME sniffing, insecure transport, caching of sensitive pages, etc. They are a **defense-in-depth** layer: the browser is the enforcement point. ## Where Spring Security produces them Spring Security wires a servlet filter (`HeaderWriterFilter`) that runs a set of `HeaderWriter` implementations. You configure it through the `HttpSecurity` DSL: `http.headers(headers -> ...)`, backed by `HeadersConfigurer`. The key point for a junior: **you get a secure baseline with zero configuration** the moment a `SecurityFilterChain` exists. ## The default headers (Spring Security 6) - **Cache-Control: no-cache, no-store, max-age=0, must-revalidate** (plus `Pragma: no-cache` and `Expires: 0`). Prevents caching of authenticated pages in shared/back-button caches. Written by `CacheControlHeadersWriter`. - **X-Content-Type-Options: nosniff** — tells the browser not to guess ("sniff") a response's content type, which blocks attacks where e.g. an uploaded file is interpreted as executable script. - **X-Frame-Options: DENY** — the page may not be embedded in any `<frame>`/`<iframe>`, defeating **clickjacking**. - **Strict-Transport-Security (HSTS)** — `max-age=31536000 ; includeSubDomains`. Tells the browser to only ever use HTTPS for this host. **Only added on secure requests** (when `request.isSecure()` is true), because sending it over plain HTTP is meaningless. - **X-XSS-Protection: 0** — the header is still written but with value `0`, i.e. the legacy browser XSS auditor is explicitly **disabled**. This changed in Spring Security 5.8/6.0 following OWASP: the old `1; mode=block` value could itself introduce vulnerabilities and modern browsers removed the feature. ## What is NOT sent by default - **Content-Security-Policy (CSP)** — powerful but app-specific, so you must opt in. - **Referrer-Policy** — also opt-in. ## How to change them Everything lives inside `http.headers(...)`. Examples: `.frameOptions(f -> f.sameOrigin())`, `.contentSecurityPolicy(csp -> csp.policyDirectives("..."))`, `.cacheControl( CacheControlConfig::disable )`, or `.httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(...))`. You can disable the whole block with `.headers(HeadersConfigurer::disable)` (rarely a good idea). ## Gotchas - HSTS silently absent in local/HTTP dev — that's expected, not a bug. - Cache-Control no-store can break serving of cacheable static assets if your security filter chain covers them; scope the chain or relax caching for static paths. - Behind a TLS-terminating proxy the request may look non-secure to the app, so HSTS won't be added unless forwarded-headers handling is configured.

  • Why isn't the Strict-Transport-Security header showing up in my local dev environment?
    HSTS is only written on secure (HTTPS) requests by default. Over plain HTTP `request.isSecure()` is false, so `HstsHeaderWriter` skips it — which is correct, since HSTS over HTTP would be ignored/meaningless anyway.
  • Are Content-Security-Policy and Referrer-Policy part of the defaults?
    No. Both must be explicitly configured via `.contentSecurityPolicy(...)` and `.referrerPolicy(...)`. They're app-specific enough that Spring won't guess a policy for you.

saying these in an interview costs you the question

  • Claiming Content-Security-Policy is sent by default
  • Saying HSTS is added on every request including plain HTTP
  • Thinking you must write config to get any security headers at all
  • Believing X-XSS-Protection default is '1; mode=block' in Spring Security 6

context

open as a page

What is X-Frame-Options, how does Spring Security set it by default, and how do you change it (e.g. for an H2 console or same-origin iframe)?

level: middleimportance: should knowfreq 55%

basics

~10 s

X-Frame-Options controls whether your page can be embedded in a frame, preventing clickjacking. Spring Security defaults to DENY. To allow same-origin framing (e.g. the H2 console) use headers().frameOptions(frame -> frame.sameOrigin()).

open as a page

How do you configure Content-Security-Policy in Spring Security, why isn't it on by default, and how does report-only mode work?

level: seniorimportance: should knowfreq 45%

basics

~10 s

CSP restricts which sources a page may load scripts/styles/etc. from, mitigating XSS. It isn't a Spring default because policies are app-specific. Configure it with headers().contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'")).

open as a page

Explain HSTS (Strict-Transport-Security): Spring Security's defaults, its directives, and the gotchas around when it is emitted.

level: seniorimportance: should knowfreq 50%

basics

~10 s

HSTS tells browsers to always use HTTPS for a host. Spring Security sends Strict-Transport-Security: max-age=31536000; includeSubDomains, but only on secure requests. Tune it via httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(...).includeSubDomains(...).preload(...)).

open as a page

Configure Referrer-Policy in Spring Security and articulate an overall security-headers hardening strategy (defaults, nosniff, cache-control, X-XSS-Protection).

level: principalimportance: nice to knowfreq 35%

basics

~10 s

Referrer-Policy controls how much of the current URL is sent in the Referer header on navigation/requests. It's opt-in: headers().referrerPolicy(r -> r.policy(ReferrerPolicy.SAME_ORIGIN)). Combine it with the default nosniff, X-Frame-Options, HSTS, and cache-control for layered hardening.

open as a page