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

Spring Security API の Quarkus エクステンション

ユーザーには Java standard annotations for security authorizations を使用することをお勧めしますが、Quarkus は spring-security エクステンション形式で Spring Security の互換性レイヤーを提供します。

このガイドでは、Quarkus アプリケーションがよく知られている Spring Security アノテーションを利用して、ロールを使用する RESTful サービスの承認を定義する方法について説明します。

前提条件

このガイドを完成させるには、以下が必要です:

  • 約15分

  • IDE

  • JDK 11+ がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.8.1+

  • 使用したい場合、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

  • Spring Web エクステンションにある程度精通している

ソリューション

次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。

Git リポジトリーのクローンを作成: git clonehttps://github.com/quarkusio/quarkus-quickstarts.git、または アーカイブ をダウンロードします。

ソリューションは、spring-security-quickstart directory にあります。

Maven プロジェクトの作成

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

CLI
quarkus create app org.acme:spring-security-quickstart \
    --extension=spring-web,spring-security,quarkus-elytron-security-properties-file,resteasy-reactive-jackson \
    --no-code
cd spring-security-quickstart

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

Quarkus CLIのインストール方法については、Quarkus CLIガイドをご参照ください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.11.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=spring-security-quickstart \
    -Dextensions="spring-web,spring-security,quarkus-elytron-security-properties-file,resteasy-reactive-jackson" \
    -DnoCode
cd spring-security-quickstart

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

このコマンドは、spring-webspring-security、および security-properties-file エクステンションをインポートするプロジェクトを生成します。

すでに Quarkus プロジェクトが設定されている場合は、プロジェクトのベースディレクトリーで以下のコマンドを実行することで、プロジェクトに spring-webspring-security および security-properties-file エクステンションを追加することができます。

CLI
quarkus extension add 'spring-web,spring-security,quarkus-elytron-security-properties-file,resteasy-reactive-jackson'
Maven
./mvnw quarkus:add-extension -Dextensions="spring-web,spring-security,quarkus-elytron-security-properties-file,resteasy-reactive-jackson"
Gradle
./gradlew addExtension --extensions="spring-web,spring-security,quarkus-elytron-security-properties-file,resteasy-reactive-jackson"

これにより、 pom.xml に以下が追加されます:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-spring-web</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-spring-security</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-elytron-security-properties-file</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-spring-web")
implementation("io.quarkus:quarkus-spring-security")
implementation("io.quarkus:quarkus-elytron-security-properties-file")
implementation("io.quarkus:quarkus-resteasy-reactive-jackson")

security-properties-file の詳細については、quarkus-elytron-security-properties-file エクステンションのガイドを確認してください。

GreetingController

Quarkus Maven プラグインは、REST エンドポイントを定義するために Spring Web アノテーションを使用してコントローラーを自動的に生成しました (デフォルトで使用される JAX-RS エンドポイントの代わりに)。まず、以下のように、REST エンドポイントを定義する Spring Web アノテーションを持つコントローラーである src/main/java/org/acme/spring/web/GreetingController.java を作成します。

package org.acme.spring.security;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/greeting")
public class GreetingController {

    @GetMapping
    public String hello() {
        return "hello";
    }
}

GreetingControllerTest

コントローラーのテストも作成されていることに注意してください。

package org.acme.spring.security;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class GreetingControllerTest {

    @Test
    public void testHelloEndpoint() {
        given()
          .when().get("/greeting")
          .then()
             .statusCode(200)
             .body(is("hello"));
    }

}

アプリケーションをパッケージ化して実行する

アプリケーションを実行します。

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

ブラウザで http://localhost:8080/greeting を開きます。

結果は {"message": "hello"} のようになります。

コントローラーを変更して、hello メソッドを保護します。

hello メソッドへのアクセスを特定のロールを持つユーザーに制限するために、@Secured アノテーションが使用されます。更新されたコントローラーは次のようになります。

package org.acme.spring.security;

import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/greeting")
public class GreetingController {

    @Secured("admin")
    @GetMapping
    public String hello() {
        return "hello";
    }
}

この例でユーザーとロールを設定する最も簡単な方法は、security-properties-file エクステンションを使用することです。このエクステンションにより、基本的に、ユーザーとロールをメインの Quarkus 設定ファイル (application.properties) で定義できます。このエクステンションの詳細については、the associated guide を確認してください。設定例は以下のとおりです。

quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true
quarkus.security.users.embedded.users.scott=jb0ss
quarkus.security.users.embedded.roles.scott=admin,user
quarkus.security.users.embedded.users.stuart=test
quarkus.security.users.embedded.roles.stuart=user

テストも更新する必要があることに注意してください。以下のようになります。

GreetingControllerTest

package org.acme.spring.security;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class GreetingControllerTest {

    @Test
    public void testHelloEndpointForbidden() {
        given().auth().preemptive().basic("stuart", "test")
                .when().get("/greeting")
                .then()
                .statusCode(403);
    }

    @Test
    public void testHelloEndpoint() {
        given().auth().preemptive().basic("scott", "jb0ss")
                .when().get("/greeting")
                .then()
                .statusCode(200)
                .body(is("hello"));
    }

}

変更をテストする

アクセスが許可されました

ブラウザーを http://localhost:8080/greeting で再度開いて、表示されたダイアログに scottjb0ss を導入します。

hello という単語が表示されます。

アクセス禁止

ブラウザーを http://localhost:8080/greeting で再度開いて、表示されたダイアログを空にします。

結果は次のようになります。

Access to localhost was denied
You don't have authorization to view this page.
HTTP ERROR 403

アプリケーションをネイティブ実行可能ファイルとして実行する

以下を使用して、ネイティブ実行可能ファイルを生成することができます。

CLI
quarkus build --native
Maven
./mvnw package -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native

サポートされている Spring Security 機能

Quarkus は現在、Spring Security が提供する機能のサブセットのみをサポートしていますが、今後はさらに多くの機能のサポートが計画されています。より具体的には、Quarkus は、ロールベースの承認セマンティクスのセキュリティー関連機能をサポートします (@RolesAllowed ではなく @Secured を考えてください)。

アノテーション

下の表は、サポートされているアノテーションをまとめたものです。

Table 1. サポートされているSpring Securityアノテーション
Name Comments

@Secured

@PreAuthorize

詳細については、次のセクションを参照してください

@PreAuthorize

Quarkus は、Spring Security の @PreAuthorize アノテーションで最もよく使用される機能のいくつかをサポートします。サポートされている式は次のとおりです。

hasRole

現在のユーザーが特定のロールを持っているかどうかをテストするには、hasRole 式を @PreAuthorize 内で使用します。

一部の例として、@PreAuthorize("hasRole('admin')")@PreAuthorize("hasRole(@roles.USER)") があります。この roles は、以下のように定義できる Bean になります。

import org.springframework.stereotype.Component;

@Component
public class Roles {

    public final String ADMIN = "admin";
    public final String USER = "user";
}
hasAnyRole

hasRole と同じように、ユーザーは hasAnyRole を使用して、ログインしているユーザーが指定されたロールのいずれかを持っているかどうかを確認できます。

一部の例として、@PreAuthorize("hasAnyRole('admin')")@PreAuthorize("hasAnyRole(@roles.USER, 'view')") があります。

permitAll

メソッドに @PreAuthorize("permitAll()") を追加すると、すべてのユーザー (匿名ユーザーを含む) がそのメソッドにアクセスできるようになります。これをクラスに追加すると、他の Spring Security アノテーションが付けられていないクラスのすべてのパブリックメソッドにアクセスできるようになります。

denyAll

メソッドに @PreAuthorize("denyAll()") を追加すると、どのユーザーもそのメソッドにアクセスできないようになります。これをクラスに追加すると、他の Spring Security アノテーションが付けられていないクラスのすべてのパブリックメソッドにどのユーザーもアクセスできないようになります。

isAnonymous

Bean メソッドに @PreAuthorize("isAnonymous()") のアノテーションを付ける場合、メソッドにアクセスできるのは、現在のユーザーが匿名の場合、つまりログインしていないユーザーの場合のみです。

isAuthenticated

Bean メソッドに @PreAuthorize("isAuthenticated()") でアノテーションを付ける場合、現在のユーザーがログインしているユーザーである場合にのみ、メソッドにアクセスできます。基本的に、このメソッドは匿名ユーザーのみが利用できません。

#paramName == authentication.principal.username

この構文により、ユーザーは、保護されたメソッドのパラメーター (またはパラメーターのフィールド) がログインしたユーザー名と等しいかどうかを確認できます。

このユースケースの例は次のとおりです。

public class Person {

    private final String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

@Component
public class MyComponent {

    @PreAuthorize("#username == authentication.principal.username") (1)
    public void doSomething(String username, String other){

    }

    @PreAuthorize("#person.name == authentication.principal.username") (2)
    public void doSomethingElse(Person person){

    }
}
1 現在ログインしているユーザーが username メソッドパラメーターと同じである場合、doSomething を実行できます
2 現在ログインしているユーザーが person メソッドパラメーターの name フィールドと同じである場合、doSomethingElse を実行できます
authentication. の使用はオプションであるため、principal.username を使用しても同じ結果になります。
#paramName != authentication.principal.username

これは前の式と似ていますが、メソッドパラメーターがログインしているユーザー名とは異なる必要があるという違いがあります。

@beanName.method()

この構文により、開発者は、特定の Bean のメソッドの実行によって、現在のユーザーが保護されたメソッドにアクセスできるかどうかを決定するように指定できます。

構文の説明には、例を使用するとわかりやすいでしょう。MyComponent Bean が以下のように作成されたと仮定しましょう。

@Component
public class MyComponent {

    @PreAuthorize("@personChecker.check(#person, authentication.principal.username)")
    public void doSomething(Person person){

    }
}

doSomething メソッドは、現在のユーザーが doSomething メソッドを呼び出すことを許可されているかどうかを判断するために、personChecker`という名前の Bean のメソッド `check を呼び出す必要があることを示す式を使用して、@PreAuthorize のアノテーションが付けられています。

PersonChecker の例は次のとおりです。

@Component
public class PersonChecker {

    @Override
    public boolean check(Person person, String username) {
        return person.getName().equals(username);
    }
}

check メソッドの場合、パラメータータイプは @PreAuthorize で指定されたものと一致する必要があり、戻り型は boolean である必要があることに注意してください。

式を組み合わせる

@PreAuthorize アノテーションは、論理 AND / OR を使用した式の組み合わせを可能にします。現在、単一の論理演算しか使用できないという制限があります (つまり、ANDOR を混在させることはできません)。

許可される式の例は次のとおりです。

    @PreAuthorize("hasAnyRole('user', 'admin') AND #user == principal.username")
    public void allowedForUser(String user) {

    }

    @PreAuthorize("hasRole('user') OR hasRole('admin')")
    public void allowedForUserOrAdmin() {

    }

    @PreAuthorize("hasAnyRole('view1', 'view2') OR isAnonymous() OR hasRole('test')")
    public void allowedForAdminOrAnonymous() {

    }

また、現在、括弧はサポートされておらず、式は必要に応じて左から右に評価されることに注意してください。

重要な技術的な注意点

Quarkus での Spring サポートは、Spring Application Context を開始せず、Spring インフラストラクチャークラスも実行しないことに注意してください。Spring クラスとアノテーションは、メタデータの読み取りにのみ使用されるか、ユーザーコードメソッドの戻り値の型またはパラメーター型として使用されます。エンドユーザーにとってそれが意味することは、任意の Spring ライブラリーを追加しても効果がないということです。さらに、Spring インフラストラクチャークラス (たとえば、org.springframework.beans.factory.config.BeanPostProcessor など) は実行されません。

変換テーブル

次の表は、Spring SecurityアノテーションをJAX-RSアノテーションに変換する方法を示しています。

Spring JAX-RS Comments

@Secured("admin")

@RolesAllowed("admin")