Understanding Cross-Site Request Forgery and Spring Security Protection
CSRF is a type of malicious exploit where unauthorized commands are transmitted from a user that the web application trusts. It tricks a user's browser into executing unwanted actions on a web application in which they're currently authenticated.
Imagine you're logged into your bank account. A malicious website could contain this hidden form:
<!-- This form is hidden on a malicious website -->
<form id="maliciousTransfer" action="https://yourbank.com/transfer" method="POST" style="display:none;">
<input type="hidden" name="to" value="attacker@evil.com" />
<input type="hidden" name="amount" value="1000" />
</form>
<script>
// Auto-submit when page loads
document.getElementById('maliciousTransfer').submit();
</script>
Problem: Your browser automatically includes your authentication cookies with this request, making the bank think YOU initiated the transfer!
CSRF tokens solve the fundamental problem: How can a server distinguish between legitimate requests from the user and forged requests from malicious sites?
Spring Security provides built-in CSRF protection that's enabled by default. Here's how it works:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers("/api/public/**") // Exclude specific endpoints
)
.authorizeHttpRequests(authz -> authz
.requestMatchers("/login", "/register").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
<!-- Spring automatically includes CSRF token in forms -->
<form th:action="@{/transfer}" method="post">
<!-- This hidden input is automatically added by Thymeleaf -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<input type="text" name="recipient" placeholder="Recipient"/>
<input type="number" name="amount" placeholder="Amount"/>
<button type="submit">Transfer Money</button>
</form>
// Include CSRF token in AJAX requests
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader(header, token);
}
});
// Or include in request body
$.post("/transfer", {
recipient: "john@example.com",
amount: 100,
_token: token
});
This form demonstrates how Spring Security protects against CSRF attacks:
@Bean
public CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
repository.setParameterName("_csrf");
return repository;
}
http.csrf(csrf -> csrf
.requireCsrfProtectionMatcher(request ->
// Only protect state-changing operations
!request.getMethod().equals("GET") &&
!request.getMethod().equals("HEAD") &&
!request.getMethod().equals("OPTIONS")
)
);
@Component
public class CustomCsrfFailureHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException exception) throws IOException {
if (exception instanceof CsrfException) {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.getWriter().write("CSRF token validation failed");
}
}
}
This form simulates a CSRF attack that submits a POST request with a hidden `_method=DELETE` to delete a student record.
Before clicking 'Simulate CSRF Attack', configure your Spring Boot SecurityConfig like this:
// Step 1: Import Customizer.withDefaults
import static org.springframework.security.config.Customizer.withDefaults;
// Step 2: Define SecurityFilterChain bean
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.ignoringRequestMatchers("/students/**") // Disable CSRF for student endpoints
)
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.formLogin(withDefaults()) // Enable default form login
.httpBasic(withDefaults()); // Enable HTTP Basic authentication
return http.build();
}
// Step 3: Enable HiddenHttpMethodFilter if using _method override for DELETE
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new HiddenHttpMethodFilter();
}
HiddenHttpMethodFilter.