When submitting information via an HTML form, Spring Security requires the form to include a CSRF (Cross-Site Request Forgery) token. This token is generated by the Spring app and tied to the user's session. Spring will use it to verify that the form submission is legitimate and not malicious. The token is required on POST, PUT, DELETE, and PATCH requests to protect against CSRF attacks. So when your user is submitting information, the form used to submit said information will need to contain a valid csrf token. This means adding a hidden field to the form that contains the token. In this post I'll demonstrate my reusable solution to this when using Spring and JTE.
Adding the hidden field
In the below example logout form jte template, the csrf param is used to create the hidden field. In this scenario the
controller will be required to set the csrf
param as a model attribute.
@import org.springframework.security.web.csrf.CsrfToken
@param CsrfToken csrf
<form action="/logout" method="post">
<input type="hidden" name="${csrf.parameterName}" value="${csrf.token}"/>
<input type="submit" value="Log out" />
</form>
But the question is, how can I make it easy to add this to all my forms? And how can I do it without having to pass the token to the view in each controller method?
The solution is to create a JTE template that can be included in all forms. This template will read the csrf token,
which will be supplied via a @ControllerAdvice
annotated class on each request. The result will be a JTE template that
can be used like so:
@import org.springframework.security.web.csrf.CsrfToken
@param CsrfToken csrf
<form action="/logout" method="post">
@template.components.csrf(csrf = csrf)
<input type="submit" value="Log out" />
</form>
Implementation
To make the csrf component work without passing the csrf token to each view model, we will create a class with
a @ControllerAdvice
annotation. This will allow us to annotate a method with @ModelAttribute
to set the csrf
param.
@ControllerAdvice
public class CsrfControllerAdvice {
@ModelAttribute
public void addCsrfToken(Model model, CsrfToken token) {
model.addAttribute("csrf", token);
}
}
The csrf jte template will look as follows.
@import org.springframework.security.web.csrf.CsrfToken
@param CsrfToken csrf
@if(csrf != null)
<input type="hidden" name="${csrf.getParameterName()}" value="${csrf.getToken()}">
@endif
The one downside with this is we will still have to pass the csrf
value to the template as a parameter. The upside is
the controller does not have to be updated to add the model attribute due to the CsrfControllerAdvice
class.