HSTS, Frames and Other Headers

Hardening the Rest

http.headers(headers -> headers
    .httpStrictTransportSecurity(hsts -> hsts
        .includeSubDomains(true)
        .preload(true)                              // eligible for the browser HSTS preload list
        .maxAgeInSeconds(Duration.ofDays(365).toSeconds()))
    .frameOptions(frame -> frame.deny())            // or sameOrigin() for your own iframes
    .referrerPolicy(referrer -> referrer
        .policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
    .addHeaderWriter(new StaticHeadersWriter("Permissions-Policy",
        "camera=(), microphone=(), geolocation=(self), payment=(self)"))
    .addHeaderWriter(new StaticHeadersWriter("Cross-Origin-Opener-Policy", "same-origin")));
HeaderWhy
Referrer-Policystops full URLs (with order ids or reset tokens) leaking to other sites in the Referer header
Permissions-Policyturns off browser features you do not use, even if a script is injected
Cross-Origin-Opener-Policyisolates your window from pop-ups on other origins

HTTPS Behind a Load Balancer

HSTS is only sent on HTTPS requests. In production TLS usually ends at the load balancer or Nginx, so the app sees plain HTTP. Tell Spring to trust the proxy's X-Forwarded-Proto header:

application.yml

server:
  forward-headers-strategy: framework   # or "native" on Tomcat behind a trusted proxy

Note: Only enable preload once every subdomain supports HTTPS — after submitting to hstspreload.org, browsers will refuse plain HTTP for your domain for a long time.

Practice

Exercise: The seller portal must be embeddable in an iframe on https://partners.shopease.in only. Which header settings do you use?

Show answer
http.headers(headers -> headers
    .frameOptions(frame -> frame.disable())     // X-Frame-Options cannot list other origins
    .contentSecurityPolicy(csp -> csp.policyDirectives(
        "default-src 'self'; frame-ancestors 'self' https://partners.shopease.in")));