templates.plugins.spincast-forms-protection.spincast-forms-protection.html Maven / Gradle / Ivy
Show all versions of spincast-website Show documentation
{% extends "../../layout.html" %}
{% block sectionClasses %}plugins hasBreadCrumb plugins-spincast-forms-protection{% endblock %}
{% block meta_title %}Plugins - Spincast Forms Protection{% endblock %}
{% block meta_description %}Spincast Forms Protection plugin - CSRF attacks and double submission protection{% endblock %}
{% block scripts %}
{% endblock %}
{% block body %}
Overview
This plugin provides ways of protecting your forms.
Currently, it can protect them from CSRF attacks
and from unwanted Double Submissions.
CSRF protection
Introduction
The CSRF protection is achieved by combining two technics:
-
Validating the
origin
and referer
headers.
-
Validating a special
protection id
that is added to the form before it is submitted.
Our implementation is an adaptation of OWASP suggestions.
Filter
The first thing to do to enable CSRF protection in your application is to add the provided
SpincastFormsCsrfProtectionFilter
"before" filter.
Here's how we suggest you add the filter :
{% verbatim %}
router.ALL()
.pos(-100)
.found()
.skipResourcesRequests()
.handle(spincastFormsCsrfProtectionFilter::handle);
{% endverbatim %}
Explanation :
-
1 : The filter will be called for every kind of
HTTP method. It is going to manage by itself what kind of method can be dangerous
or not.
-
2 : It needs to be a
before filter
, but you can adjust
the exact position by taking into account your other filters.
-
3 : Only the regular routes ("
found
") should be
protected. You don't want to run the filter again if an exception occurs, for example.
-
4 : There is no need to protect
resources.
-
5 : The
SpincastFormsCsrfProtectionFilter
filter's handler.
Adding CSRF protection ids to forms
If you use the default templating engine, Pebble, two functions are provided
by the plugin to easily add CSRF protection ids to your forms:
-
formCsrfProtectionFieldName()
:
Outputs the name of the field to use to store the
CSRF protection ids. In general, you want to use it on a hidden field.
-
formCsrfProtectionFieldValue()
:
Outputs the CSRF protection id to use.
For example:
{% verbatim %}
<form>
<input type="hidden"
name="{{ formCsrfProtectionFieldName() }}"
value="{{ formCsrfProtectionFieldValue() }}" />
// other fields...
</form>
{% endverbatim %}
If you are not using Pebble, you have to generate the same
field using those:
-
SpincastFormsProtectionConfig#getFormCsrfProtectionIdFieldName()
:
to get the name of the field to use.
-
SpincastFormsCsrfProtectionFilter#getCurrentCsrfToken()
:
to get the protection id to use.
That's it, your forms are now protected against CSRF attacks!
Note that once the CSRF filter is added, all forms must send a
CSRF protection id, otherwise they will be rejected!
Configuration
Have a look at the Configurations section to learn the
available options to tweak this CSRF protection.
Double Submit protection
Introduction
The Double Submit protection works that way: once a form is submitted with a
protection id, this id is saved on the server. If the same form (id) is submitted again,
a filter will reject it.
The form will also be rejected if it is too old. The lifespan of a form is
configurable.
Implementing the repository
The Double Submit protection is agnostic on how the protection ids of the submitted forms are stored
on the server. This is why you have to provide the code for saving and retrieving the ids.
You do this by binding an implementation of the
SpincastFormsDoubleSubmitProtectionRepository
interface. There are three methods to implement :
-
void saveSubmittedFormProtectionId(Instant date, String protectedId)
Called by the plugin when the protection id
of a submitted form
needs to be saved.
-
boolean isFormAlreadySubmitted(String protectedId)
Called by the plugin to see if a form was already submitted.
-
void deleteOldFormsProtectionIds(int maxAgeMinutes)
Called by the plugin to delete the protection ids
that have been saved for more than "maxAgeMinutes
" minutes.
When your implementation is ready, you bind it in your application's
Guice module:
{% verbatim %}
bind(SpincastFormsDoubleSubmitProtectionRepository.class)
.to(YourAppFormsDoubleSubmitProtectionRepository.class)
.in(Scopes.SINGLETON);
{% endverbatim %}
Finally, here's an example of an SQL script (targeting PostgreSQL
) to create a
"forms_submitted
" table. You will probably have to adjust this query
for your own database! Or even to use something totally different if you
are not using a relational database.
{% verbatim %}
CREATE TABLE forms_submitted (
id VARCHAR(36) PRIMARY KEY,
creation_date TIMESTAMPTZ NOT NULL
)
{% endverbatim %}
Filter
You also have to add a provided
SpincastFormsDoubleSubmitProtectionFilter
"before" filter to your router.
Here's how we suggest you add the filter:
{% verbatim %}
router.ALL()
.pos(-200)
.found()
.skipResourcesRequests()
.handle(spincastFormsDoubleSubmitProtectionFilter::handle);
{% endverbatim %}
Explanation :
-
1 : The filter should be called for every kind of
HTTP method.
-
2 : It needs to be a
before filter
, but you can adjust
the exact position by taking into account your other filters.
-
3 : Only the regular routes ("
found
") should be
protected. You don't need to run the filter again if an exception occurs, for example.
-
4 : There is no need to protect
resources.
-
5 : The
SpincastFormsDoubleSubmitProtectionFilter
filter's handler.
Adding Double Submit protection ids to forms
If you use the default templating engine, Pebble, three functions are provided
by the plugin to easily add a Double Submit protection id to a form:
-
formDoubleSubmitProtectionFieldName()
:
This fonction will output the name of the field to use to store the
Double Submit protection id. In general, you want to use it on a hidden field.
-
formDoubleSubmitProtectionFieldValue()
:
This fonction will output the value of the Double Submit protection id to be used.
For example:
{% verbatim %}
<form>
<input type="hidden"
name="{{ formDoubleSubmitProtectionFieldName() }}"
value="{{ formDoubleSubmitProtectionFieldValue() }}" />
// other fields...
</form>
{% endverbatim %}
If you are not using Pebble, you have to generate the same
field using those:
-
SpincastFormsProtectionConfig#getFormDoubleSubmitProtectionIdFieldName()
:
to get the name of the field to use.
-
SpincastFormsDoubleSubmitProtectionFilter#createNewFormDoubleSubmitProtectionId()
:
to get the protection id to use.
And that's it, your form is protected agains Double Submissions!
Disabling the Double Submit protection
Note that, by default, once the Double Submit filter is added, all forms must send a
protection id, otherwise they will be rejected!
But the Double Submit protection is not always required. In fact, it sometimes even
have to be disabled! For example, you may have a form that is used to upload
images via Ajax. This form may well be used more than once and it's perfectly fine!
If you use Pebble, you can easily disable a form from being protected by using this function:
-
formDoubleSubmitDisableProtectionFieldName()
In your form, you add a field (most of the time an hidden field)
with the name this function outputs and set any value to it ("1
" for example):
{% verbatim %}
<form>
<input type="hidden"
name="{{ formDoubleSubmitDisableProtectionFieldName() }}"
value="1" />
// other fields...
</form>
{% endverbatim %}
When the SpincastFormsDoubleSubmitProtectionFilter
filter sees that a submitted
form contains this field, it won't validate the form at all.
Configuration
Have a look at the Configurations section to learn the
available options to tweak this Double Submit protection.
Dependencies
This plugin depends on three plugins which are not
provided by default by the spincast-default
artifact:
Those dependencies will be automatically installed, but you may need to
configure them! For example, the Spincast Session
plugin will
add a default repository that uses cookies
in order to store the sessions... you may want to bind a custom one instead. It will also bind
two filters, by default at positions -100
and 100
. You may want to bind a custom implementation
of SpincastSessionConfig
if you need to modify those positions!
In other words, make sure you read the documentation of the automatically installed plugins!
Installation
As you learn in the previous sections, you need to add two "before" filters,
bind an implementation for the
SpincastFormsDoubleSubmitProtectionRepository
interface, and optionally also bind some custom configurations.
Other than that, the plugin is installed as any other one:
1.
Add this Maven artifact to your project:
<dependency>
<groupId>org.spincast</groupId>
<artifactId>spincast-plugins-forms-protection</artifactId>
<version>{{spincast.spincastCurrrentVersion}}</version>
</dependency>
2. Add an instance of the SpincastFormsProtectionPlugin
plugin to your Spincast Bootstrapper:
{% verbatim %}
Spincast.configure()
.plugin(new SpincastFormsProtectionPlugin())
// ...
{% endverbatim %}
Configurations
The configuration interface for this plugin is
SpincastFormsProtectionConfig.
To change the default configurations, you can bind an implementation of that interface, extending the default
SpincastFormsProtectionConfigDefault
implementation if you don't want to start from scratch.
Options:
-
String getFormCsrfProtectionIdFieldName()
The name of the field (in general a hidden field) to use
in a form for CSRF protection.
Defaults to "spincast_csrf_id
".
-
String getFormDoubleSubmitProtectionIdFieldName()
The name of the field (in general a hidden field) to use
in a form for Double Submit protection.
Defaults to "spincast_ds_id
".
-
String getFormDoubleSubmitDisableProtectionIdFieldName()
The name of the field (in general a hidden field) to use
in a form to disable Double Submit protection.
Defaults to "spincast_ds_disabled
".
-
String autoRegisterDeleteOldDoubleSubmitProtectionIdsScheduledTask()
Should the plugin automatically register a scheduled task that will call
deleteOldFormsProtectionIds()
to clean old Double Submit protection ids?
Defaults to true
.
-
int getDeleteOldDoubleSubmitProtectionIdsScheduledTaskRunEveryNbrMinutes()
If autoRegisterDeleteOldDoubleSubmitProtectionIdsScheduledTask()
is true
, this configuration specifies the number of minutes between
two launches of the scheduled task.
Defaults to 15
.
-
int getFormDoubleSubmitFormValidForNbrMinutes()
The number of minutes a form is considered as valid. If the form is
older than the specified number of minutes, it will be rejected by the
Double Submit protection filter.
Defaults to 120
(2 hours).
{% endblock %}