Implementing CSRF Protection in Java Web Apps
Cross-Site Request Forgery (CSRF) is a common security vulnerability in web applications where a malicious user can trick a logged-in user into executing unwanted actions on a website. This can lead to unintended actions such as changing account settings, making unauthorized transactions, or submitting forms without the user’s consent.
To prevent CSRF attacks in Java web applications, it is crucial to implement protection mechanisms that ensure that every request is legitimate and comes from the intended user.
1. What is CSRF?
CSRF occurs when an attacker tricks a user into performing an action on a web application where they are already authenticated, such as making an unauthorized request (e.g., changing a password, deleting an account). The attacker typically uses social engineering tactics to get the user to execute the malicious action, often by embedding a request in an email, website, or other social media.
Example of CSRF:
Let’s say a user is logged into their banking app and the application allows the user to transfer funds. The attacker can trick the user into clicking on a link that automatically triggers a transfer of money without the user’s knowledge.
For example:
<img src="http://example.com/transfer?amount=1000&toAccount=attackerAccount" />
This image tag would cause a request to be sent to the banking app when the user visits a malicious website, transferring funds without the user’s consent.
2. CSRF Protection Strategies
1. Using CSRF Tokens (Anti-CSRF Token)
The most common and effective way to prevent CSRF attacks is by using CSRF tokens. These tokens are unique values generated by the server and included in every form or state-changing request. The server verifies that the token is correct before processing the request, ensuring that the request was made intentionally by the user.
How it Works:
- A unique token is generated by the server for every form that modifies data (POST, PUT, DELETE).
- The token is embedded in the form (or in headers for AJAX requests).
- When the form is submitted, the token is sent with the request.
- The server validates the token before processing the request. If the token is missing or invalid, the request is rejected.
Example of CSRF Token Implementation in a Java Web App (Spring Security)
If you’re using Spring Security, CSRF protection is enabled by default. Here’s how to configure and use it:
<security:http>
<!-- CSRF Protection enabled by default -->
<security:csrf />
</security:http>
For non-GET requests (like POST, PUT, DELETE), Spring automatically includes a CSRF token in the response and checks that the token is included in subsequent requests. You can manually embed the CSRF token in your forms like this:
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="${_csrf.token}"> <!-- CSRF Token -->
<input type="text" name="amount" />
<input type="text" name="toAccount" />
<input type="submit" value="Transfer" />
</form>
In this example, the "_csrf"
token is included as a hidden input field, and the token is verified on the server when the form is submitted.
Spring Security CSRF Protection – Example (Controller Method):
@Controller
public class TransferController {
@PostMapping("/transfer")
public String transferFunds(@RequestParam String amount, @RequestParam String toAccount) {
// Process transfer here after CSRF token verification
return "transferSuccess";
}
}
With Spring Security, CSRF protection is automatically handled for requests made through forms, but you can customize it further if needed.
2. SameSite Cookies (Browser-based Protection)
Modern browsers support the SameSite cookie attribute, which provides an additional layer of protection against CSRF. By setting this attribute to Strict
or Lax
, cookies will not be sent on cross-site requests, preventing attackers from using cookies to impersonate a user.
To enable SameSite cookies in Java applications, you can configure your web server or application to send cookies with the SameSite
attribute.
Example (Setting SameSite cookies in a Java Servlet):
Cookie cookie = new Cookie("user_session", sessionId);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
cookie.setMaxAge(60 * 60); // 1 hour
cookie.setSameSite("Strict"); // SameSite attribute
response.addCookie(cookie);
- Strict: Cookies are only sent in a first-party context (i.e., not sent when the user is redirected from another site).
- Lax: Cookies are sent in a first-party context and when navigating to the URL (e.g., when clicking a link).
- None: Cookies are sent in all contexts (requires
Secure
flag).
Setting SameSite=Strict is the most secure option, but it may break legitimate cross-site functionality. SameSite=Lax is a good balance between security and usability.
3. Double Submit Cookies
Another approach to CSRF protection is the Double Submit Cookie pattern. In this approach, two values are sent for each request: one as a cookie and another as a request parameter. The server checks if both values match to validate the request.
How it Works:
- The server sends a unique CSRF token as a cookie to the browser.
- The client sends this token as a part of the request (usually as a header or parameter).
- The server checks if the token in the request matches the token in the cookie. If they don’t match, the request is rejected.
Example of Double Submit Cookies (Servlet Example):
// Set the CSRF token in the response cookie
Cookie csrfCookie = new Cookie("X-CSRF-TOKEN", csrfToken);
csrfCookie.setHttpOnly(true);
csrfCookie.setSecure(true);
csrfCookie.setPath("/");
response.addCookie(csrfCookie);
// Include CSRF token in the request header or form
When the request is made, the CSRF token is validated by comparing the cookie value and the request value.
4. Custom Headers for AJAX Requests
For AJAX requests, ensure that a valid CSRF token is included in the HTTP headers. You can set the token as a custom header (such as X-CSRF-Token
) and verify it on the server side.
Example of CSRF Token in AJAX Requests (JavaScript):
fetch('/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken // Set CSRF token in the header
},
body: JSON.stringify({
amount: 1000,
toAccount: 'recipient123'
})
})
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));
On the server side, you would validate the CSRF token in the request header and reject any requests with an invalid token.
3. Configuring CSRF Protection in Other Java Frameworks
- Servlet-based applications: If you’re not using a framework like Spring, you can implement CSRF protection manually by generating a CSRF token on the server and embedding it in forms as a hidden input field. You would then compare the token in the request with the one stored on the server.
- Java EE / Jakarta EE: CSRF protection can be implemented using filters or interceptors that check for CSRF tokens in HTTP requests.