skip to content

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%

answer

  1. Referrer-Policy = control Referer leakage, opt-in
  2. STRICT_ORIGIN_WHEN_CROSS_ORIGIN = balanced default
  3. nosniff/DENY/no-store/HSTS = keep the defaults
  4. X-XSS-Protection deliberately 0 → use CSP
  5. browser defense-in-depth, not authz replacement

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.

solid answer

~40 s

Referrer-Policy governs how much of the originating URL the browser includes in the `Referer` header when navigating or loading resources, preventing leakage of sensitive paths/query strings to third parties. It's not a Spring default; enable it via `.referrerPolicy(r -> r.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))` (a common privacy-preserving choice). Strategically, treat headers as layered defense: keep the secure defaults (nosniff to stop MIME sniffing, X-Frame-Options: DENY for clickjacking, no-store cache-control for authenticated pages, HSTS on HTTPS), then opt into the app-specific ones — CSP (the strongest XSS mitigation, with frame-ancestors superseding X-Frame-Options) and Referrer-Policy. Note X-XSS-Protection now defaults to '0' by design, since the legacy auditor is harmful; rely on CSP instead. Validate the live headers (curl -I, securityheaders.com), and remember these are browser-enforced defense-in-depth, never a replacement for server-side authz, validation, and output encoding.

code

java · 13 lines
java
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.headers(headers -> headers
        // Keep secure defaults (nosniff, X-Frame-Options DENY, HSTS, cache-control) as-is,
        // then opt into the app-specific hardening headers:
        .referrerPolicy(ref -> ref.policy(ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
        .contentSecurityPolicy(csp -> csp
            .policyDirectives("default-src 'self'; object-src 'none'; frame-ancestors 'self'"))
    );
    return http.build();
}

go deeper

for a junior

Know Referrer-Policy limits the Referer header and is opt-in.

for a middle

Pick a sensible policy value and configure it via the DSL.

for a senior

Explain why X-XSS-Protection is 0 and how the default set fits together.

for a principal

Design an end-to-end, proxy-aware hardening strategy and articulate the browser-vs-server boundary.

## Referrer-Policy When a browser follows a link or loads a resource, it normally sends a **`Referer`** header (the historic misspelling) containing the URL of the page the request came from. That URL can leak sensitive data — session tokens in query strings, internal path structure, account ids — to third-party sites and analytics. **Referrer-Policy** lets the server dictate how much of that URL is exposed. Common values (enum `ReferrerPolicyHeaderWriter.ReferrerPolicy`): - `NO_REFERRER` — send nothing. - `SAME_ORIGIN` — full URL only to same-origin requests, nothing cross-origin. - `STRICT_ORIGIN` — send only the origin (scheme+host), and only when protocol security isn't downgraded. - `STRICT_ORIGIN_WHEN_CROSS_ORIGIN` — full URL same-origin, origin-only cross-origin, nothing on HTTPS→HTTP. This is the modern browser default and a good balanced choice. - `ORIGIN`, `UNSAFE_URL`, etc. Configure (opt-in — not a Spring default): ```java http.headers(h -> h.referrerPolicy(r -> r.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))); ``` Written by `ReferrerPolicyHeaderWriter`. ## X-Content-Type-Options: nosniff (default) Browsers may 'sniff' a response body to guess its type, ignoring the declared `Content-Type`. Attackers exploit this to make e.g. an uploaded 'image' execute as JavaScript. `nosniff` forbids sniffing — the browser trusts the declared type. Spring sends it by default. ## Cache-Control (default) Spring writes `Cache-Control: no-cache, no-store, max-age=0, must-revalidate` (+ `Pragma`/`Expires`) so authenticated responses aren't cached in shared caches or retrievable via the back button after logout. Disable per-need with `.cacheControl(cache -> cache.disable())` — e.g. to let genuinely static, non-sensitive assets be cached (better: serve those from a chain without security caching). ## X-XSS-Protection: 0 (default, deliberate) The legacy header controlled a now-removed browser 'XSS auditor'. That auditor introduced its own vulnerabilities (info leaks, false-positive breakage), so OWASP and browsers deprecated it. Spring Security 6 therefore writes **`X-XSS-Protection: 0`** — explicitly off — and you should rely on **CSP** for XSS defense, not this header. ## A coherent hardening strategy (principal view) 1. **Keep the secure defaults** — don't reflexively disable the headers block. nosniff, DENY, no-store, HSTS(HTTPS) are free wins. 2. **Add CSP** — the single highest-value addition for XSS; roll out via report-only, use nonces/hashes over `'unsafe-inline'`, and let `frame-ancestors` govern framing (superseding X-Frame-Options). 3. **Add Referrer-Policy** — `STRICT_ORIGIN_WHEN_CROSS_ORIGIN` or stricter to stop URL leakage. 4. **Handle proxies** — ensure `X-Forwarded-Proto` is trusted so HSTS/secure detection works in production. 5. **Scope exceptions narrowly** — per-chain/per-path matchers rather than global weakening (e.g. only relax framing for an admin console). 6. **Verify in prod** — inspect real responses (`curl -I`, browser devtools, external header scanners) since proxies/CDNs can strip or add headers. 7. **Remember the boundary** — these headers are browser-enforced defense-in-depth. They do NOT replace server-side authorization (`@PreAuthorize`), input validation, CSRF protection, or output encoding; they harden the last mile in the client. ## Gotchas - Referrer-Policy has no Spring default — silent absence means full URLs may leak. - A CDN/proxy can override or drop your headers; validate end-to-end. - Disabling cache-control globally to fix caching of static files can expose sensitive pages — scope it. - Don't 'restore' X-XSS-Protection: 1 thinking it helps; it can reintroduce vulnerabilities.

  • Why does Spring Security 6 write X-XSS-Protection: 0 instead of enabling it?
    The legacy browser XSS auditor it controlled was buggy — it caused information leaks and false-positive page breakage — and modern browsers removed it. OWASP now recommends disabling it. Spring writes '0' to explicitly turn it off; real XSS defense should come from CSP and output encoding.
  • Where do these HTTP security headers sit relative to server-side controls like @PreAuthorize?
    They're complementary defense-in-depth enforced by the browser, not a substitute. @PreAuthorize/authorization, input validation, CSRF tokens, and output encoding enforce security server-side; the headers harden the client against clickjacking, MIME sniffing, transport downgrade, XSS, and referrer leakage. You need both layers.

saying these in an interview costs you the question

  • Assuming Referrer-Policy is emitted by default
  • Re-enabling X-XSS-Protection: 1 believing it improves security
  • Treating headers as a replacement for authorization/validation/encoding
  • Globally disabling cache-control and exposing authenticated pages to caches

context