-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CrossSite Request Forgery prevention filter
- Loading branch information
1 parent
8018aaa
commit 618b8d5
Showing
23 changed files
with
1,128 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
//// | ||
This guide is maintained in the main Quarkus repository | ||
and pull requests should be submitted there: | ||
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc | ||
//// | ||
= Cross-Site Request Forgery Prevention | ||
|
||
include::./attributes.adoc[] | ||
|
||
https://owasp.org/www-community/attacks/csrf[Cross-Site Request Forgery(CSRF)] is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated. | ||
|
||
Quarkus Security provides a CSRF prevention feature which consists of a xref:resteasy-reactive.adoc[Resteasy Reactive] server filter which creates and verifies CSRF tokens and an HTML form parameter provider which supports the xref:qute-reference.adoc#injecting-beans-directly-in-templates[injection of CSRF tokens in Qute templates]. | ||
|
||
== Creating the Project | ||
|
||
First, we need a new project. | ||
Create a new project with the following command: | ||
|
||
:create-app-artifact-id: security-csrf-prevention | ||
:create-app-extensions: csrf-reactive | ||
include::{includes}/devtools/create-app.adoc[] | ||
|
||
This command generates a project which imports the `csrf-reactive` extension. | ||
|
||
If you already have your Quarkus project configured, you can add the `csrf-reactive` extension | ||
to your project by running the following command in your project base directory: | ||
|
||
:add-extension-extensions: csrf-reactive | ||
include::{includes}/devtools/extension-add.adoc[] | ||
|
||
This will add the following to your build file: | ||
|
||
[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] | ||
.pom.xml | ||
---- | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-csrf-reactive</artifactId> | ||
</dependency> | ||
---- | ||
|
||
[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] | ||
.build.gradle | ||
---- | ||
implementation("io.quarkus:quarkus-csrf-reactive") | ||
---- | ||
|
||
Next lets add a Qute template producing an HTML form: | ||
|
||
[source,html] | ||
---- | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<title>User Name Input</title> | ||
</head> | ||
<body> | ||
<h1>User Name Input</h1> | ||
<form action="/service/csrfTokenForm" method="post"> | ||
<input type="hidden" name="{inject:csrf.parameterName}" value="{inject:csrf.token}" /> <1> | ||
<p>Your Name: <input type="text" name="name" /></p> | ||
<p><input type="submit" name="submit"/></p> | ||
</form> | ||
</body> | ||
</html> | ||
---- | ||
|
||
<1> This expression is used to inject a CSRF token into a hidden form field. This token will be verified by the CSRF filter against a CSRF cookie. | ||
|
||
You can name the file containing this template as `csrfToken.html` and put it in a `src/main/resources/templates` folder. | ||
|
||
Now let's create a resource class which returns an HTML form and handles form POST requests: | ||
|
||
[source,java] | ||
---- | ||
package io.quarkus.it.csrf; | ||
import javax.inject.Inject; | ||
import javax.ws.rs.Consumes; | ||
import javax.ws.rs.FormParam; | ||
import javax.ws.rs.GET; | ||
import javax.ws.rs.POST; | ||
import javax.ws.rs.Path; | ||
import javax.ws.rs.Produces; | ||
import javax.ws.rs.core.MediaType; | ||
import io.quarkus.qute.Template; | ||
import io.quarkus.qute.TemplateInstance; | ||
@Path("/service") | ||
public class UserNameResource { | ||
@Inject | ||
Template csrfToken; <1> | ||
@GET | ||
@Path("/csrfTokenForm") | ||
@Produces(MediaType.TEXT_HTML) | ||
public TemplateInstance getCsrfTokenForm() { | ||
return csrfToken.instance(); <2> | ||
} | ||
@POST | ||
@Path("/csrfTokenForm") | ||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED) | ||
@Produces(MediaType.TEXT_PLAIN) | ||
public String postCsrfTokenForm(@FormParam("name") String name) { | ||
return userName; <3> | ||
} | ||
} | ||
---- | ||
|
||
<1> Inject the `csrfToken.html` as a `Template`. | ||
<2> Return HTML form with a hidden form field containing a CSRF token created by the CSRF filter. | ||
<3> Handle the form POST request, this method can only be invoked only if the CSRF filter has successfully verified the token. | ||
|
||
The form POST request will fail with HTTP status `400` if the filter finds the hidden CSRF form field is missing, the CSRF cookie is missing, or if the CSRF form field and CSRF cookie values do not match. | ||
|
||
At this stage no additional configuration is needed - by default the CSRF form field and cookie name will be set to `csrf_token`, and the filter will verify the token. But lets change these names: | ||
|
||
[source,properties] | ||
---- | ||
quarkus.csrf-reactive.form-field-name=csrftoken | ||
quarkus.csrf-reactive.cookie-name=csrftoken | ||
---- | ||
|
||
Note that the CSRF filter has to read the input stream in order to verify the token and then re-create the stream for the application code to read it as well. The filter performs this work on an event loop thread so for small form payloads such as the one shown in the example above it will have negligible peformance side-effects. However if you deal with large form payloads then it is recommended to compare the CSRF form field and cookie values in the application code: | ||
|
||
[source,java] | ||
---- | ||
package io.quarkus.it.csrf; | ||
import javax.inject.Inject; | ||
import javax.ws.rs.BadRequestException; | ||
import javax.ws.rs.Consumes; | ||
import javax.ws.rs.CookieParam; | ||
import javax.ws.rs.FormParam; | ||
import javax.ws.rs.GET; | ||
import javax.ws.rs.POST; | ||
import javax.ws.rs.Path; | ||
import javax.ws.rs.Produces; | ||
import javax.ws.rs.core.MediaType; | ||
import io.quarkus.qute.Template; | ||
import io.quarkus.qute.TemplateInstance; | ||
@Path("/service") | ||
public class UserNameResource { | ||
@Inject | ||
Template csrfToken; | ||
@GET | ||
@Path("/csrfTokenForm") | ||
@Produces(MediaType.TEXT_HTML) | ||
public TemplateInstance getCsrfTokenForm() { | ||
return csrfToken.instance(); | ||
} | ||
@POST | ||
@Path("/csrfTokenForm") | ||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED) | ||
@Produces(MediaType.TEXT_PLAIN) | ||
public String postCsrfTokenForm(@CookieParam("csrf-token") csrfCookie, @FormParam("csrf-token") String formCsrfToken, @FormParam("name") String userName) { | ||
if (!csrfCookie.getValue().equals(formCsrfToken)) { <1> | ||
throw new BadRequestException(); | ||
} | ||
return userName; | ||
} | ||
} | ||
---- | ||
|
||
<1> Compare the CSRF form field and cookie values and fail with HTTP status `400` if they don't match. | ||
|
||
Also disable the token verification in the filter: | ||
|
||
[source,properties] | ||
---- | ||
quarkus.csrf-reactive.verify-token=false | ||
---- | ||
|
||
|
||
[[csrf-reactive-configuration-reference]] | ||
== Configuration Reference | ||
|
||
include::{generated-dir}/config/quarkus-csrf-reactive.adoc[leveloffset=+1, opts=optional] | ||
|
||
== References | ||
|
||
* https://owasp.org/www-community/attacks/csrf[OWASP Cross-Site Request Forgery] | ||
* xref:resteasy-reactive.adoc[RESTEasy Reactive] | ||
* xref:qute-reference.adoc[Qute Reference] | ||
* xref:security.adoc[Quarkus Security] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<parent> | ||
<artifactId>quarkus-csrf-reactive-parent</artifactId> | ||
<groupId>io.quarkus</groupId> | ||
<version>999-SNAPSHOT</version> | ||
<relativePath>../</relativePath> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>quarkus-csrf-reactive-deployment</artifactId> | ||
<name>Quarkus - Cross-Site Request Forgery Filter Reactive - Deployment</name> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-csrf-reactive</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-resteasy-reactive-deployment</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-resteasy-reactive-qute-deployment</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-core-deployment</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-arc-deployment</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-vertx-http-deployment</artifactId> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<configuration> | ||
<annotationProcessorPaths> | ||
<path> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-extension-processor</artifactId> | ||
<version>${project.version}</version> | ||
</path> | ||
</annotationProcessorPaths> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
14 changes: 14 additions & 0 deletions
14
...deployment/src/main/java/io/quarkus/csrf/reactive/CsrfReactiveAlwaysEnabledProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package io.quarkus.csrf.reactive; | ||
|
||
import io.quarkus.deployment.annotations.BuildStep; | ||
import io.quarkus.deployment.builditem.FeatureBuildItem; | ||
|
||
// Executed even if the extension is disabled, see https://github.com/quarkusio/quarkus/pull/26966/ | ||
public class CsrfReactiveAlwaysEnabledProcessor { | ||
|
||
@BuildStep | ||
FeatureBuildItem featureBuildItem() { | ||
return new FeatureBuildItem("csrf-reactive"); | ||
} | ||
|
||
} |
35 changes: 35 additions & 0 deletions
35
...srf-reactive/deployment/src/main/java/io/quarkus/csrf/reactive/CsrfReactiveBuildStep.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package io.quarkus.csrf.reactive; | ||
|
||
import java.util.function.BooleanSupplier; | ||
|
||
import io.quarkus.arc.deployment.AdditionalBeanBuildItem; | ||
import io.quarkus.csrf.reactive.runtime.CsrfRequestResponseReactiveFilter; | ||
import io.quarkus.csrf.reactive.runtime.CsrfTokenParameterProvider; | ||
import io.quarkus.deployment.annotations.BuildProducer; | ||
import io.quarkus.deployment.annotations.BuildStep; | ||
import io.quarkus.deployment.annotations.BuildSteps; | ||
import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem; | ||
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; | ||
|
||
@BuildSteps(onlyIf = CsrfReactiveBuildStep.IsEnabled.class) | ||
public class CsrfReactiveBuildStep { | ||
|
||
@BuildStep | ||
void registerProvider(BuildProducer<AdditionalBeanBuildItem> additionalBeans, | ||
BuildProducer<ReflectiveClassBuildItem> reflectiveClass, | ||
BuildProducer<AdditionalIndexedClassesBuildItem> additionalIndexedClassesBuildItem) { | ||
additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(CsrfRequestResponseReactiveFilter.class)); | ||
reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, CsrfRequestResponseReactiveFilter.class)); | ||
additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(CsrfTokenParameterProvider.class)); | ||
additionalIndexedClassesBuildItem | ||
.produce(new AdditionalIndexedClassesBuildItem(CsrfRequestResponseReactiveFilter.class.getName())); | ||
} | ||
|
||
public static class IsEnabled implements BooleanSupplier { | ||
CsrfReactiveBuildTimeConfig config; | ||
|
||
public boolean getAsBoolean() { | ||
return config.enabled; | ||
} | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
...active/deployment/src/main/java/io/quarkus/csrf/reactive/CsrfReactiveBuildTimeConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package io.quarkus.csrf.reactive; | ||
|
||
import io.quarkus.runtime.annotations.ConfigItem; | ||
import io.quarkus.runtime.annotations.ConfigRoot; | ||
|
||
/** | ||
* Build time configuration for CSRF Reactive Filter. | ||
*/ | ||
@ConfigRoot | ||
public class CsrfReactiveBuildTimeConfig { | ||
/** | ||
* If filter is enabled. | ||
*/ | ||
@ConfigItem(defaultValue = "true") | ||
public boolean enabled; | ||
} |
Oops, something went wrong.