The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.
Back to Guides

クロスサイトリクエストフォージェリー対策

クロスサイトリクエストフォージェリ(CSRF) とは、エンドユーザーが現在認証されているウェブアプリケーション上で意図しないアクションを実行させる攻撃です。

Quarkus Security provides a CSRF prevention feature which implements a Double Submit Cookie technique. This techninque requires that the CSRF token is never directly exposed to scripts executed on the client-side. In this extension, the CSRF token is:

  • sent as HTTPOnly cookie to the client, and

  • directly embedded in a hidden form input of server-side rendered forms, which are transmitted to and used by the client.

The extension consists of a RESTEasy Reactive server filter which creates and verifies CSRF tokens in application/x-www-form-urlencoded and multipart/form-data forms and a Qute HTML form parameter provider which supports the injection of CSRF tokens in Qute templates.

プロジェクトの作成

まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します:

コマンドラインインタフェース
quarkus create app org.acme:security-csrf-prevention \
    --extension='csrf-reactive' \
    --no-code
cd security-csrf-prevention

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストール方法や使用方法については、<a href="cli-tooling.html">Quarkus CLIガイド</a> を参照してください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.16.4.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=security-csrf-prevention \
    -Dextensions='csrf-reactive' \
    -DnoCode
cd security-csrf-prevention

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

このコマンドは、 csrf-reactive エクステンションをインポートするプロジェクトを生成します。

Quarkusプロジェクトをすでに設定している場合は、プロジェクトのベースディレクトリで次のコマンドを実行することで、 csrf-reactive エクステンションをプロジェクトに追加できます:

コマンドラインインタフェース
quarkus extension add 'csrf-reactive'
Maven
./mvnw quarkus:add-extension -Dextensions='csrf-reactive'
Gradle
./gradlew addExtension --extensions='csrf-reactive'

これにより、ビルドファイルに以下が追加されます:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-csrf-reactive</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-csrf-reactive")

次に、このテンプレートを含むファイルを csrfToken.html と名付け、 src/main/resources/templates フォルダに置いてください:

<!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 この書式は、CSRF トークンを隠しフォームフィールドに注入するために使用されます。このトークンは CSRF フィルタによって CSRF クッキーと照合され検証されます。

では、HTMLフォームを返し、フォームのPOSTリクエストを処理するリソースクラスを作成しましょう:

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 userName) {
        return userName; (3)
    }
}
1 csrfToken.htmlTemplate として注入してください。
2 CSRFフィルターによって作成されたCSRFトークンを含むhiddenフォームフィールドを持つHTMLフォームを返します。
3 Handle the POST form request, this method can only be invoked if the CSRF filter has successfully verified the token.

フィルタが、隠された CSRF フォームフィールドがない、CSRF クッキーがない、あるいは CSRF フォームフィールドと CSRF クッキーの値が一致しないことを発見した場合、フォーム POST リクエストは HTTP ステータス 400 で失敗します。

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 you can change these names if you would like:

quarkus.csrf-reactive.form-field-name=csrftoken
quarkus.csrf-reactive.cookie-name=csrftoken

Sign CSRF token

You can get HMAC signatures created for the generated CSRF tokens and have these HMAC values stored as CSRF token cookies if you would like to avoid the risk of the attackers recreating the CSRF cookie token. All you need to do is to configure a token signature secret which must be at least 32 characters long:

quarkus.csrf-reactive.token-signature-key=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow

Restrict CSRF token verification

Your JAX-RS endpoint may accept not only HTTP POST requests with application/x-www-form-urlencoded or multipart/form-data payloads but also payloads with other media types, either on the same or different URL paths, and therefore you would like to avoid verifying the CSRF token in such cases, for example:

package io.quarkus.it.csrf;

import javax.inject.Inject;
import javax.ws.rs.BadRequestException;
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;

    @GET
    @Path("/user")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance getCsrfTokenForm() {
        return csrfToken.instance();
    }

    (1)
    @POST
    @Path("/user")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.TEXT_PLAIN)
    public String postCsrfTokenForm(@FormParam("name") String userName) {
        return userName;
    }

    (2)
    @POST
    @Path("/user")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.TEXT_PLAIN)
    public String postJson(User user) {
        return user.name;
    }

    (3)
    @POST
    @Path("/users")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.TEXT_PLAIN)
    public String postJson(User user) {
        return user.name;
    }

    public static class User {
        private String name;
        public String getName() {
            return this.name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
}
1 POST form request to /user, CSRF token verification is enforced by the CSRF filter
2 POST json request to /user, CSRF token verification is not needed
3 POST json request to /users, CSRF token verification is not needed

As you can see a CSRF token verification will be required at the /service/user path accepting the application/x-www-form-urlencoded payload, but User JSON representation posted to both /service/user and /service/users method will have no CSRF token and therefore the token verification has to be skipped in these cases by restricting it to the specific /service/user request path but also allowing not only application/x-www-form-urlencoded on this path:

# Verify CSRF token only for the `/service/user` path, ignore other paths such as `/service/users`
quarkus.csrf-reactive.create-token-path=/service/user

# If `/service/user` path accepts not only `application/x-www-form-urlencoded` payloads but also other ones such as JSON then allow them
quarkus.csrf-reactive.require-form-url-encoded=false

Verify CSRF token in the application code

If you prefer to compare the CSRF form field and cookie values in the application code then you can do it as follows:

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 CSRFのフォームフィールドとクッキーの値を比較し、一致しない場合はHTTPステータス 400 で失敗します。

また、フィルターのトークン検証を無効にします:

quarkus.csrf-reactive.verify-token=false

設定リファレンス

ビルド時に固定される設定プロパティ - その他の設定プロパティはランタイムでオーバーライド可能です。

Configuration property

デフォルト

If filter is enabled.

Environment variable: QUARKUS_CSRF_REACTIVE_ENABLED

boolean

true

Form field name which keeps a CSRF token.

Environment variable: QUARKUS_CSRF_REACTIVE_FORM_FIELD_NAME

string

csrf-token

CSRF cookie name.

Environment variable: QUARKUS_CSRF_REACTIVE_COOKIE_NAME

string

csrf-token

CSRF cookie max age.

Environment variable: QUARKUS_CSRF_REACTIVE_COOKIE_MAX_AGE

Duration

10M

CSRF cookie path.

Environment variable: QUARKUS_CSRF_REACTIVE_COOKIE_PATH

string

/

CSRF cookie domain.

Environment variable: QUARKUS_CSRF_REACTIVE_COOKIE_DOMAIN

string

If enabled the CSRF cookie will have its 'secure' parameter set to 'true' when HTTP is used. It may be necessary when running behind an SSL terminating reverse proxy. The cookie will always be secure if HTTPS is used even if this property is set to false.

Environment variable: QUARKUS_CSRF_REACTIVE_COOKIE_FORCE_SECURE

boolean

false

Create CSRF token only if the HTTP GET relative request path matches one of the paths configured with this property. Use a comma to separate multiple path values.

Environment variable: QUARKUS_CSRF_REACTIVE_CREATE_TOKEN_PATH

list of string

Random CSRF token size in bytes.

Environment variable: QUARKUS_CSRF_REACTIVE_TOKEN_SIZE

int

16

CSRF token HMAC signature key, if this key is set then it must be at least 32 characters long.

Environment variable: QUARKUS_CSRF_REACTIVE_TOKEN_SIGNATURE_KEY

string

Verify CSRF token in the CSRF filter. If this property is enabled then the input stream will be read and cached by the CSRF filter to verify the token. If you prefer then you can disable this property and compare CSRF form and cookie parameters in the application code using JAX-RS javax.ws.rs.FormParam which refers to the form-field-name form property and javax.ws.rs.CookieParam which refers to the CsrfReactiveConfig#cookieName cookie. Note that even if the CSRF token verification in the CSRF filter is disabled, the filter will still perform checks to ensure the token is available, has the correct token-size in bytes and that the Content-Type HTTP header is either 'application/x-www-form-urlencoded' or 'multipart/form-data'.

Environment variable: QUARKUS_CSRF_REACTIVE_VERIFY_TOKEN

boolean

true

Require that only 'application/x-www-form-urlencoded' or 'multipart/form-data' body is accepted for the token verification to proceed. Disable this property for the CSRF filter to avoid verifying the token for POST requests with other content types. This property is only effective if verify-token property is enabled.

Environment variable: QUARKUS_CSRF_REACTIVE_REQUIRE_FORM_URL_ENCODED

boolean

true

期間フォーマットについて

期間のフォーマットは標準の java.time.Duration フォーマットを使用します。詳細は Duration#parse() javadoc を参照してください。

数値で始まる期間の値を指定することもできます。この場合、値が数値のみで構成されている場合、コンバーターは値を秒として扱います。そうでない場合は、 PT が暗黙的に値の前に付加され、標準の java.time.Duration 形式が得られます。