Spring Security CSRF Protection
Introduction
Cross-Site Request Forgery (CSRF) is a common web security vulnerability where unauthorized commands are submitted from a user that the web application trusts. In this attack, if a user is authenticated on a site, an attacker can trick them into executing unwanted actions without their knowledge.
Spring Security provides built-in protection against CSRF attacks, which we'll explore in this tutorial. You'll learn what CSRF is, how it works, and how to implement and customize Spring Security's CSRF protection in your applications.
What is CSRF?
CSRF (sometimes pronounced "sea-surf") is an attack that forces authenticated users to submit a request to a web application against which they're currently authenticated. This can lead to unauthorized actions being performed on behalf of the authenticated user.
How a CSRF Attack Works
- A user logs into a legitimate website (like a banking site)
- The site sets a session cookie in the user's browser
- Without logging out, the user visits a malicious website
- The malicious site contains code that submits a form to the banking site
- Since the browser automatically includes cookies with requests, the banking site thinks the request is legitimate
- The banking site processes the request (like transferring money) thinking it came from the authenticated user
Spring Security's CSRF Protection
Spring Security prevents CSRF attacks using a synchronizer token pattern. This approach includes:
- Generating a random token associated with the user's session
- Including this token in all forms as a hidden field
- Validating the token on the server for every state-changing request (POST, PUT, DELETE, etc.)
Default Configuration
By default, Spring Security enables CSRF protection. Here's what a simple configuration looks like:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// Other security configurations
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
return http.build();
}
}
The CookieCsrfTokenRepository.withHttpOnlyFalse()
stores the CSRF token in a cookie that JavaScript can read, which is useful for single-page applications.
Using CSRF Protection in Forms
When using Thymeleaf with Spring, the CSRF token is automatically included in forms:
<form th:action="@{/process}" method="post">
<!-- form fields -->
<button type="submit">Submit</button>
</form>
Behind the scenes, Thymeleaf adds a hidden input field with the CSRF token:
<input type="hidden" name="_csrf" value="a7726543-2f..." />
Manual CSRF Token Inclusion
If you're not using Thymeleaf or another template engine that automatically includes the token, you'll need to add it manually:
<form action="/process" method="post">
<!-- form fields -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<button type="submit">Submit</button>
</form>
CSRF Protection in RESTful Applications
In RESTful applications or Single Page Applications (SPAs), you'll need to handle CSRF differently:
- Configure Spring Security to use a cookie-based CSRF token repository:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
return http.build();
}
}
- In your JavaScript code, extract the CSRF token and include it in your AJAX requests:
// Utility function to get CSRF token from cookie
function getCsrfToken() {
const cookieValue = document.cookie
.split('; ')
.find(row => row.startsWith('XSRF-TOKEN='))
?.split('=')[1];
return cookieValue;
}
// Example fetch request with CSRF token
async function submitData(data) {
const response = await fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': getCsrfToken() // Include CSRF token in header
},
body: JSON.stringify(data)
});
return response.json();
}
Disabling CSRF Protection
There are some cases where you might want to disable CSRF protection, such as:
- For stateless REST APIs that use tokens (like JWT)
- For testing purposes
Note: Disabling CSRF protection is generally not recommended for web applications that use session cookies for authentication.
Here's how to disable CSRF protection:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// Other security configurations
.csrf().disable();
return http.build();
}
}
Disabling CSRF Protection for Specific Endpoints
You might want to disable CSRF protection only for certain endpoints, such as public APIs:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf()
.ignoringAntMatchers("/api/public/**")
.and()
// Other security configurations
return http.build();
}
}
In Spring Security 6.0 and above, the configuration is slightly different:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/public/**")
)
// Other security configurations
return http.build();
}
}
Custom CSRF Token Repository
You can also create a custom CSRF token repository if you have specific requirements:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(new CustomCsrfTokenRepository())
// Other security configurations
return http.build();
}
}
public class CustomCsrfTokenRepository implements CsrfTokenRepository {
// Implement the required methods
@Override
public CsrfToken generateToken(HttpServletRequest request) {
// Generate a unique token
}
@Override
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
// Save the token (e.g., in a cookie or session)
}
@Override
public CsrfToken loadToken(HttpServletRequest request) {
// Load the token from wherever it was saved
}
}
Real-World Example: Secure Banking Transfer Form
Let's implement a simple money transfer form with proper CSRF protection:
- Create a controller:
@Controller
public class BankController {
@GetMapping("/transfer")
public String showTransferForm(Model model) {
model.addAttribute("transferRequest", new TransferRequest());
return "transfer";
}
@PostMapping("/transfer")
public String processTransfer(@Valid TransferRequest transferRequest,
BindingResult result,
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
return "transfer";
}
// Process the transfer
bankService.transferMoney(
transferRequest.getFromAccount(),
transferRequest.getToAccount(),
transferRequest.getAmount()
);
redirectAttributes.addFlashAttribute("message", "Transfer successful!");
return "redirect:/dashboard";
}
}
public class TransferRequest {
@NotEmpty(message = "From account is required")
private String fromAccount;
@NotEmpty(message = "To account is required")
private String toAccount;
@Positive(message = "Amount must be positive")
private BigDecimal amount;
// Getters and setters
}
- Create a Thymeleaf template (transfer.html):
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Money Transfer</title>
</head>
<body>
<h1>Transfer Money</h1>
<form th:action="@{/transfer}" th:object="${transferRequest}" method="post">
<div>
<label for="fromAccount">From Account:</label>
<input type="text" id="fromAccount" th:field="*{fromAccount}" />
<span th:if="${#fields.hasErrors('fromAccount')}" th:errors="*{fromAccount}"></span>
</div>
<div>
<label for="toAccount">To Account:</label>
<input type="text" id="toAccount" th:field="*{toAccount}" />
<span th:if="${#fields.hasErrors('toAccount')}" th:errors="*{toAccount}"></span>
</div>
<div>
<label for="amount">Amount:</label>
<input type="number" id="amount" th:field="*{amount}" step="0.01" />
<span th:if="${#fields.hasErrors('amount')}" th:errors="*{amount}"></span>
</div>
<button type="submit">Transfer</button>
</form>
</body>
</html>
In this example, Thymeleaf automatically adds the CSRF token to the form, protecting the money transfer from CSRF attacks.
Common Issues and Troubleshooting
1. CSRF Token Mismatch
If you're getting "Invalid CSRF Token" errors, check:
- The token is being correctly included in your requests
- You're not making cross-domain requests without proper configuration
- Your session isn't expiring before the form is submitted
2. CSRF with AJAX
When making AJAX POST/PUT/DELETE requests, ensure you're:
- Reading the CSRF token from the cookie or meta tag
- Including it in your request headers
- Using the correct header name (typically "X-CSRF-TOKEN" or "X-XSRF-TOKEN")
3. Session Timeout
If the user's session times out, the CSRF token becomes invalid. Consider implementing:
- A session timeout warning
- AJAX token refresh
Summary
CSRF attacks remain a significant security concern for web applications. Spring Security provides robust protection against these attacks through its CSRF token mechanism. Key takeaways:
- CSRF attacks trick authenticated users into performing unwanted actions
- Spring Security uses the synchronizer token pattern to prevent CSRF attacks
- CSRF protection is enabled by default in Spring Security
- For form submissions, the CSRF token is typically included as a hidden field
- For RESTful APIs, the token can be stored in a cookie and sent via headers
- Disable CSRF protection only for stateless APIs or testing purposes
By properly implementing CSRF protection, you can significantly enhance the security of your Spring applications and protect your users from this common attack vector.
Additional Resources
- Spring Security Documentation on CSRF
- OWASP CSRF Prevention Cheat Sheet
- Spring Security Architecture Guide
Exercises
- Configure a Spring Boot application with CSRF protection and create a simple form that submits data.
- Implement CSRF protection for a RESTful API using AJAX requests.
- Create a custom CSRF token repository that stores tokens in a database.
- Configure CSRF protection to be disabled for specific endpoints while keeping it enabled for others.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)