Cross-Site Request Forgery (CSRF) protection prevents unauthorized commands from being transmitted from a user that the web application trusts.
Represents expected CSRF token information.
package org.springframework.security.web.csrf;
public interface CsrfToken {
/**
* Gets the HTTP header name for the CSRF token.
*
* @return the header name (e.g., "X-CSRF-TOKEN")
*/
String getHeaderName();
/**
* Gets the HTTP parameter name for the CSRF token.
*
* @return the parameter name (e.g., "_csrf")
*/
String getParameterName();
/**
* Gets the CSRF token value.
*
* @return the token value
*/
String getToken();
}Default implementation of CsrfToken.
package org.springframework.security.web.csrf;
public final class DefaultCsrfToken implements CsrfToken {
/**
* Creates a CSRF token.
*
* @param headerName the HTTP header name
* @param parameterName the HTTP parameter name
* @param token the token value
*/
public DefaultCsrfToken(String headerName, String parameterName, String token);
public String getHeaderName();
public String getParameterName();
public String getToken();
public boolean equals(Object o);
public int hashCode();
}Manages CSRF token generation, storage, and retrieval.
package org.springframework.security.web.csrf;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public interface CsrfTokenRepository {
/**
* Generates a new CSRF token.
*
* @param request the HTTP request
* @return the generated token
*/
CsrfToken generateToken(HttpServletRequest request);
/**
* Saves the CSRF token.
*
* @param token the token to save
* @param request the HTTP request
* @param response the HTTP response
*/
void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response);
/**
* Loads the CSRF token.
*
* @param request the HTTP request
* @return the token or null if not found
*/
CsrfToken loadToken(HttpServletRequest request);
}Stores CSRF tokens in the HTTP session.
package org.springframework.security.web.csrf;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository {
public static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";
public static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN";
public static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = "org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN";
public HttpSessionCsrfTokenRepository();
public CsrfToken generateToken(HttpServletRequest request);
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response);
public CsrfToken loadToken(HttpServletRequest request);
/**
* Sets the HTTP parameter name (default: "_csrf").
*/
public void setParameterName(String parameterName);
/**
* Sets the HTTP header name (default: "X-CSRF-TOKEN").
*/
public void setHeaderName(String headerName);
/**
* Sets the session attribute name for storing the token.
*/
public void setSessionAttributeName(String sessionAttributeName);
}Stores CSRF tokens in cookies.
package org.springframework.security.web.csrf;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
public static final String DEFAULT_CSRF_COOKIE_NAME = "XSRF-TOKEN";
public static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";
public static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN";
public CookieCsrfTokenRepository();
public CsrfToken generateToken(HttpServletRequest request);
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response);
public CsrfToken loadToken(HttpServletRequest request);
/**
* Creates a repository with HttpOnly cookie disabled.
* Useful for JavaScript access.
*/
public static CookieCsrfTokenRepository withHttpOnlyFalse();
/**
* Sets the cookie name (default: "XSRF-TOKEN").
*/
public void setCookieName(String cookieName);
/**
* Sets the parameter name (default: "_csrf").
*/
public void setParameterName(String parameterName);
/**
* Sets the header name (default: "X-XSRF-TOKEN").
*/
public void setHeaderName(String headerName);
/**
* Sets the cookie path.
*/
public void setCookiePath(String cookiePath);
/**
* Sets the cookie domain.
*/
public void setCookieDomain(String cookieDomain);
/**
* Sets whether cookie is HTTP-only.
*/
public void setCookieHttpOnly(boolean cookieHttpOnly);
/**
* Sets whether cookie is secure (HTTPS only).
*/
public void setSecure(Boolean secure);
/**
* Sets the cookie max age in seconds.
*/
public void setCookieMaxAge(int cookieMaxAge);
}Applies CSRF protection to incoming requests.
package org.springframework.security.web.csrf;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
public final class CsrfFilter extends OncePerRequestFilter {
public static final RequestMatcher DEFAULT_CSRF_MATCHER = new DefaultRequiresCsrfMatcher();
/**
* Creates a CSRF filter with the given repository.
*/
public CsrfFilter(CsrfTokenRepository tokenRepository);
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException;
/**
* Sets the matcher for requests requiring CSRF protection.
* Default protects: POST, PUT, DELETE, PATCH.
*/
public void setRequireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher);
/**
* Sets the access denied handler for CSRF failures.
*/
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler);
/**
* Sets the CSRF token request handler.
*/
public void setCsrfTokenRequestHandler(CsrfTokenRequestHandler requestHandler);
}Handles CSRF tokens in requests.
package org.springframework.security.web.csrf;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public interface CsrfTokenRequestHandler {
/**
* Handles the CSRF token, making it available to the application.
*
* @param request the HTTP request
* @param response the HTTP response
* @param csrfToken the CSRF token (may be a DeferredCsrfToken)
*/
void handle(HttpServletRequest request, HttpServletResponse response,
Supplier<CsrfToken> csrfToken);
}Makes CSRF token available as a request attribute.
package org.springframework.security.web.csrf;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.function.Supplier;
public class CsrfTokenRequestAttributeHandler implements CsrfTokenRequestHandler {
public CsrfTokenRequestAttributeHandler();
public void handle(HttpServletRequest request, HttpServletResponse response,
Supplier<CsrfToken> csrfToken);
/**
* Sets the request attribute name for the CSRF token.
* Default: "org.springframework.security.web.csrf.CsrfToken"
*/
public final void setCsrfRequestAttributeName(String csrfRequestAttributeName);
}CSRF token handler that uses XOR masking for additional protection against BREACH attacks.
package org.springframework.security.web.csrf;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.function.Supplier;
public final class XorCsrfTokenRequestAttributeHandler implements CsrfTokenRequestHandler, CsrfTokenRequestResolver {
public XorCsrfTokenRequestAttributeHandler();
/**
* Handles the CSRF token, applying XOR masking.
*/
public void handle(HttpServletRequest request, HttpServletResponse response,
Supplier<CsrfToken> csrfToken);
/**
* Resolves the CSRF token value from the request.
*/
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken);
/**
* Sets the request attribute name for the CSRF token.
*/
public void setCsrfRequestAttributeName(String csrfRequestAttributeName);
}Resolves the token value from the request.
package org.springframework.security.web.csrf;
import jakarta.servlet.http.HttpServletRequest;
public interface CsrfTokenRequestResolver {
/**
* Resolves the CSRF token value from the request.
*
* @param request the HTTP request
* @param csrfToken the expected CSRF token
* @return the resolved token value or null
*/
String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken);
}Allows delayed access to a CSRF token.
package org.springframework.security.web.csrf;
import java.util.function.Supplier;
public interface DeferredCsrfToken {
/**
* Gets the CSRF token, generating it if necessary.
*
* @return the CSRF token
*/
CsrfToken get();
/**
* Returns true if get() refers to a generated token.
*
* @return true if newly generated
*/
boolean isGenerated();
}Base exception for CSRF-related failures.
package org.springframework.security.web.csrf;
import org.springframework.security.access.AccessDeniedException;
public class CsrfException extends AccessDeniedException {
public CsrfException(String message);
}Thrown when a CSRF token is present but invalid.
package org.springframework.security.web.csrf;
public class InvalidCsrfTokenException extends CsrfException {
public InvalidCsrfTokenException(CsrfToken expectedToken, String actualToken);
}Thrown when no CSRF token is found in the request.
package org.springframework.security.web.csrf;
public class MissingCsrfTokenException extends CsrfException {
public MissingCsrfTokenException(String missingHeaderName);
}import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class CsrfConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
}import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
@Configuration
public class SelectiveCsrfConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.ignoringRequestMatchers(
PathPatternRequestMatcher.pathPattern("/api/**"),
PathPatternRequestMatcher.pathPattern("/webhooks/**")
)
);
return http.build();
}
}import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.DefaultCsrfToken;
public class RedisCsrfTokenRepository implements CsrfTokenRepository {
private final RedisTemplate<String, String> redisTemplate;
private final Duration tokenDuration = Duration.ofHours(1);
public RedisCsrfTokenRepository(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public CsrfToken generateToken(HttpServletRequest request) {
String token = UUID.randomUUID().toString();
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token);
}
@Override
public void saveToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response) {
if (token == null) {
String sessionId = request.getSession().getId();
redisTemplate.delete("csrf:" + sessionId);
} else {
String sessionId = request.getSession().getId();
redisTemplate.opsForValue().set(
"csrf:" + sessionId,
token.getToken(),
tokenDuration
);
}
}
@Override
public CsrfToken loadToken(HttpServletRequest request) {
String sessionId = request.getSession().getId();
String token = redisTemplate.opsForValue().get("csrf:" + sessionId);
if (token == null) {
return null;
}
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token);
}
}<!-- Thymeleaf -->
<form method="post" th:action="@{/api/submit}">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<!-- form fields -->
<button type="submit">Submit</button>
</form>
<!-- JSP -->
<form method="post" action="/api/submit">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<!-- form fields -->
<button type="submit">Submit</button>
</form>// Using jQuery
$(function () {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
});
// Using Fetch API
const csrfToken = document.querySelector('meta[name="_csrf"]').content;
const csrfHeader = document.querySelector('meta[name="_csrf_header"]').content;
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
[csrfHeader]: csrfToken
},
body: JSON.stringify(data)
});import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.security.web.csrf.InvalidCsrfTokenException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.access.AccessDeniedException;
public class CsrfErrorHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
if (accessDeniedException instanceof CsrfException) {
CsrfException csrfException = (CsrfException) accessDeniedException;
if (csrfException instanceof MissingCsrfTokenException) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json");
response.getWriter().write(
"{\"error\": \"CSRF token missing\", \"message\": \"Please include CSRF token in request\"}"
);
} else if (csrfException instanceof InvalidCsrfTokenException) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json");
response.getWriter().write(
"{\"error\": \"Invalid CSRF token\", \"message\": \"CSRF token validation failed\"}"
);
}
} else {
// Handle other access denied cases
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
}
// Configuration
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.accessDeniedHandler(new CsrfErrorHandler())
);
return http.build();
}import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class StatelessApiConfig {
@Bean
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatchers(matchers -> matchers
.requestMatchers("/api/**")
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.csrf(csrf -> csrf.disable()) // CSRF not needed for stateless token-based auth
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt()); // JWT-based authentication
return http.build();
}
}import org.springframework.security.web.csrf.DeferredCsrfToken;
import org.springframework.security.web.csrf.CsrfToken;
@RestController
public class CsrfTokenController {
@GetMapping("/api/csrf-token")
public Map<String, String> getCsrfToken(
@RequestAttribute("org.springframework.security.web.csrf.CsrfToken")
DeferredCsrfToken deferredToken) {
CsrfToken token = deferredToken.get();
return Map.of(
"headerName", token.getHeaderName(),
"parameterName", token.getParameterName(),
"token", token.getToken()
);
}
}import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.security.web.csrf.InvalidCsrfTokenException;
import org.springframework.security.web.access.AccessDeniedHandler;
@Configuration
public class CustomCsrfConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(customCsrfTokenRepository())
.accessDeniedHandler(csrfAccessDeniedHandler())
);
return http.build();
}
@Bean
public CsrfTokenRepository customCsrfTokenRepository() {
return new CsrfTokenRepository() {
@Override
public CsrfToken generateToken(HttpServletRequest request) {
String token = UUID.randomUUID().toString();
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token);
}
@Override
public void saveToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response) {
if (token == null) {
request.getSession().removeAttribute("CSRF_TOKEN");
} else {
request.getSession().setAttribute("CSRF_TOKEN", token.getToken());
}
}
@Override
public CsrfToken loadToken(HttpServletRequest request) {
String token = (String) request.getSession().getAttribute("CSRF_TOKEN");
if (token == null) {
return null;
}
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token);
}
};
}
@Bean
public AccessDeniedHandler csrfAccessDeniedHandler() {
return (request, response, accessDeniedException) -> {
if (accessDeniedException instanceof CsrfException) {
CsrfException csrfException = (CsrfException) accessDeniedException;
// Log CSRF violation
logger.warn("CSRF token violation: {} from {}",
csrfException.getMessage(),
request.getRemoteAddr());
// For AJAX requests, return JSON
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json");
String errorType = "unknown";
if (csrfException instanceof MissingCsrfTokenException) {
errorType = "missing_token";
} else if (csrfException instanceof InvalidCsrfTokenException) {
errorType = "invalid_token";
}
response.getWriter().write(String.format(
"{\"error\": \"CSRF token %s\", \"type\": \"%s\"}",
csrfException.getMessage(), errorType
));
} else {
// For regular requests, redirect to error page
response.sendRedirect("/error/csrf");
}
} else {
// Handle other access denied exceptions
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
};
}
}import org.springframework.security.web.csrf.DeferredCsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
@Configuration
public class DeferredCsrfConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
);
return http.build();
}
}
// Using deferred token in controller
@RestController
public class CsrfTokenController {
@GetMapping("/api/csrf-token")
public Map<String, String> getCsrfToken(
@RequestAttribute("_csrf") DeferredCsrfToken deferredToken) {
CsrfToken token = deferredToken.get();
return Map.of(
"headerName", token.getHeaderName(),
"parameterName", token.getParameterName(),
"token", token.getToken()
);
}
}import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.http.HttpMethod;
@Configuration
public class SelectiveCsrfConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.requireCsrfProtectionMatcher(customCsrfMatcher())
);
return http.build();
}
private RequestMatcher customCsrfMatcher() {
// Protect all POST, PUT, DELETE, PATCH except webhooks
RequestMatcher webhookMatcher =
PathPatternRequestMatcher.pathPattern("/webhooks/**");
RequestMatcher stateChangingMethods = new RequestMatcher() {
@Override
public boolean matches(HttpServletRequest request) {
String method = request.getMethod();
return "POST".equals(method) ||
"PUT".equals(method) ||
"DELETE".equals(method) ||
"PATCH".equals(method);
}
};
// Match state-changing methods but exclude webhooks
return new AndRequestMatcher(
stateChangingMethods,
new NegatedRequestMatcher(webhookMatcher)
);
}
}import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
@Component
public class RedisCsrfTokenRepository implements CsrfTokenRepository {
private final RedisTemplate<String, String> redisTemplate;
private static final String CSRF_TOKEN_PREFIX = "csrf:";
private static final Duration TOKEN_TTL = Duration.ofHours(1);
public RedisCsrfTokenRepository(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public CsrfToken generateToken(HttpServletRequest request) {
String token = UUID.randomUUID().toString();
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token);
}
@Override
public void saveToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response) {
String sessionId = getSessionId(request);
if (token == null) {
redisTemplate.delete(CSRF_TOKEN_PREFIX + sessionId);
} else {
redisTemplate.opsForValue().set(
CSRF_TOKEN_PREFIX + sessionId,
token.getToken(),
TOKEN_TTL
);
}
}
@Override
public CsrfToken loadToken(HttpServletRequest request) {
String sessionId = getSessionId(request);
String token = redisTemplate.opsForValue().get(CSRF_TOKEN_PREFIX + sessionId);
if (token == null) {
return null;
}
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token);
}
private String getSessionId(HttpServletRequest request) {
HttpSession session = request.getSession(false);
return session != null ? session.getId() : "anonymous";
}
}