ベーシック認証によるQuarkusアプリケーションの保護
Secure your Quarkus application endpoints by combining Quarkus built-in basic HTTP authentication with the JPA identity provider to enable role-based access control (RBAC). The JPA IdentityProvider
creates a SecurityIdentity
instance, which is used during user authentication to verify and authorize access requests making your Quarkus application secure.
このチュートリアルでは、Quarkusでより高度なセキュリティメカニズムを実装するための準備、たとえば、OpenID Connect(OIDC)認証メカニズムの使用方法について説明します。
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 11+ がインストールされ、
JAVA_HOME
が適切に設定されていること -
Apache Maven 3.8.6
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
何を構築するか
このチュートリアルでは、以下のエンドポイントを提供するアプリケーションを構築する方法を説明します:
エンドポイント | 説明 |
---|---|
|
|
|
The |
|
|
コードを確かめるだけなら、次のいずれかの方法で、完成例まで早送りすることができます:
ソリューションは |
1. Create a Maven project
Quarkus セキュリティでセキュリティソースを JPA エンティティにマッピングするには、このチュートリアルで使用する Maven プロジェクト に security-jpa
エクステンションが含まれていることを確認します。 security-jpa
エクステンションを持つ新しい Maven プロジェクトを作成するか、既存の Maven プロジェクト にエクステンションを追加します。
-
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します:
ユーザー ID の保存には Hibernate ORM with Panache が使用されていますが、Hibernate ORM を使用することも可能です。また、お好みのデータベースコネクタライブラリを追加する必要があります。 このチュートリアルの例では、ID ストアに PostgreSQL データベースを使用しています。 |
-
既存の Maven プロジェクトに
security-jpa
エクステンションを追加するには、プロジェクトのベースディレクトリで次のコマンドを実行します:
quarkus extension add 'security-jpa'
./mvnw quarkus:add-extension -Dextensions='security-jpa'
./gradlew addExtension --extensions='security-jpa'
2. アプリケーションの記述
-
まず、すべてのユーザーがアプリケーションにアクセスできるように、
/api/public
エンドポイントを実装しましょう。 次のコードスニペットのように、通常のJAX-RSリソースをJavaソースコードに追加してください:package org.acme.security.jpa; import javax.annotation.security.PermitAll; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/api/public") public class PublicResource { @GET @PermitAll @Produces(MediaType.TEXT_PLAIN) public String publicResource() { return "public"; } }
-
api/admin
エンドポイントのソースコードも同様ですが、代わりに@RolesAllowed
アノテーションを使用して、admin
ロールを与えられたユーザのみがエンドポイントにアクセスできることを確認します。 JAX-RS リソースを追加して、以下の@RolesAllowed
アノテーションを付けます:package org.acme.security.jpa; import javax.annotation.security.RolesAllowed; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/api/admin") public class AdminResource { @GET @RolesAllowed("admin") @Produces(MediaType.TEXT_PLAIN) public String adminResource() { return "admin"; } }
-
最後に、
/api/users/me
エンドポイントを考えてみましょう。下のソースコードを見ればわかるように、user
の役割を持つユーザのみを信頼しています。現在認証されているPrincipal
へのアクセスを得るためにSecurityContext
を使用しており、ユーザーの名前を返します。この情報はデータベースから読み込まれます。package org.acme.security.jpa; import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Context; import javax.ws.rs.core.SecurityContext; @Path("/api/users") public class UserResource { @GET @RolesAllowed("user") @Path("/me") public String me(@Context SecurityContext securityContext) { return securityContext.getUserPrincipal().getName(); } }
2.1. ユーザーエンティティーの定義
次のコードで説明するように、user
エンティティにアノテーションを追加することで、セキュリティ情報をどのようにモデルに格納したいかを記述することができます:
package org.acme.security.jpa;
import javax.persistence.Entity;
import javax.persistence.Table;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import io.quarkus.elytron.security.common.BcryptUtil;
import io.quarkus.security.jpa.Password;
import io.quarkus.security.jpa.Roles;
import io.quarkus.security.jpa.UserDefinition;
import io.quarkus.security.jpa.Username;
@Entity
@Table(name = "test_user")
@UserDefinition (1)
public class User extends PanacheEntity {
@Username (2)
public String username;
@Password (3)
public String password;
@Roles (4)
public String role;
/**
* Adds a new user in the database
* @param username the username
* @param password the unencrypted password (it will be encrypted with bcrypt)
* @param role the comma-separated roles
*/
public static void add(String username, String password, String role) { (5)
User user = new User();
user.username = username;
user.password = BcryptUtil.bcryptHash(password);
user.role = role;
user.persist();
}
}
security-jpa
エクステンションは、 @UserDefinition
でアノテーションされた単一のエンティティーがある場合にのみ初期化されます。
1 | アノテーション @UserDefinition は単一のエンティティに存在する必要があり、通常の Hibernate ORM エンティティまたは Panache 付き Hibernate ORM エンティティのいずれかにすることができます。 |
2 | ユーザー名に使用されるフィールドを示します。 |
3 | パスワードに使用するフィールドを示します。デフォルトではbcryptハッシュ化されたパスワードを使用しますが、プレーンテキストまたはカスタムパスワード用に設定することも可能です。 |
4 | ここでは対象のプリンシパル表現属性に追加されたロールのコンマ区切りリストを示します。 |
5 | この方法では、パスワードを適切なbcryptハッシュでハッシュしながらユーザーを追加することができます。 |
2.2. アプリケーションの設定
-
Enable Quarkus built-in basic HTTP authentication by setting the
quarkus.http.auth.basic
property totrue
:quarkus.http.auth.basic
=true`When secure access is required and no other authentication mechanisms are enabled, Quarkus built-in basic HTTP authentication is the fallback authentication mechanism. Therefore, in this tutorial, you do not need to set the property
quarkus.http.auth.basic=true
. -
少なくとも1つのデータソースを設定し、
security-jpa
エクステンションがデータベースにアクセスできるようにします。quarkus.http.auth.basic=true quarkus.datasource.db-kind=postgresql quarkus.datasource.username=quarkus quarkus.datasource.password=quarkus quarkus.datasource.jdbc.url=jdbc:postgresql:security_jpa quarkus.hibernate-orm.database.generation=drop-and-create
-
データベースをユーザーとロールで初期化するには、次のコードで説明するように
Startup
クラスを実装します:
このチュートリアルでは、PostgreSQL データベースを ID ストアに使用します。Hibernate ORM は起動時にデータベーススキーマを自動的に作成します (運用時にはこれを変更します)。 |
package org.acme.security.jpa;
import javax.enterprise.event.Observes;
import javax.inject.Singleton;
import javax.transaction.Transactional;
import io.quarkus.runtime.StartupEvent;
@Singleton
public class Startup {
@Transactional
public void loadUsers(@Observes StartupEvent evt) {
// reset and load all test users
User.deleteAll();
User.add("admin", "admin", "admin");
User.add("user", "user", "user");
}
}
これでアプリケーションは保護され、ユーザーIDは指定されたデータベースから提供されるようになりました。
本番環境では、プレーンテキストのパスワードは保存しないでください。 そのため、 |
3. アプリケーションのテスト
3.1. Use Dev Services for PostgreSQL to test your application
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
アプリケーションを実稼働モードで動作させる前に、統合テストを追加してみましょう。
JVMとネイティブモードでのアプリケーションの統合テストには、 Dev Services for PostgreSQL を使用します。
以下のプロパティ構成は、PostgreSQLのテストを本番環境( prod
)モードのみで実行できるようにする方法を示しています。このシナリオでは、 Dev Services for PostgreSQL
が PostgreSQL
テストコンテナを起動し、構成します。
%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.username=quarkus
%prod.quarkus.datasource.password=quarkus
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql:elytron_security_jpa
quarkus.hibernate-orm.database.generation=drop-and-create
プロファイルの接頭辞に %prod.
を付けると、データソースのプロパティは Dev Services for PostgreSQL
からは見えなくなり、本番モードで動作するアプリケーションからのみ表示されるようになります。
統合テストを書くには、次のコードサンプルを使用します:
package org.acme.elytron.security.jpa;
import static io.restassured.RestAssured.get;
import static io.restassured.RestAssured.given;
import static org.hamcrest.core.Is.is;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class JpaSecurityRealmTest {
@Test
void shouldAccessPublicWhenAnonymous() {
get("/api/public")
.then()
.statusCode(HttpStatus.SC_OK);
}
@Test
void shouldNotAccessAdminWhenAnonymous() {
get("/api/admin")
.then()
.statusCode(HttpStatus.SC_UNAUTHORIZED);
}
@Test
void shouldAccessAdminWhenAdminAuthenticated() {
given()
.auth().preemptive().basic("admin", "admin")
.when()
.get("/api/admin")
.then()
.statusCode(HttpStatus.SC_OK);
}
@Test
void shouldNotAccessUserWhenAdminAuthenticated() {
given()
.auth().preemptive().basic("admin", "admin")
.when()
.get("/api/users/me")
.then()
.statusCode(HttpStatus.SC_FORBIDDEN);
}
@Test
void shouldAccessUserAndGetIdentityWhenUserAuthenticated() {
given()
.auth().preemptive().basic("user", "user")
.when()
.get("/api/users/me")
.then()
.statusCode(HttpStatus.SC_OK)
.body(is("user"));
}
}
このコードサンプルからわかるように、テストコードからテストコンテナーを起動する必要はありません。
If you start your application in dev mode, |
3.2. curl
またはブラウザを使用して、アプリケーションをテストします
下記の例のように、PostgreSQL サーバーを起動します:
docker run --rm=true --name security-getting-started -e POSTGRES_USER=quarkus \
-e POSTGRES_PASSWORD=quarkus -e POSTGRES_DB=elytron_security_jpa \
-p 5432:5432 postgres:14.1
3.3. アプリケーションのコンパイルと実行
次のいずれかの方法で、Quarkusアプリケーションをコンパイルして実行します:
3.4. アプリケーションのセキュリティへのアクセスおよびテスト
アプリケーションの実行中は、以下の curl
コマンドのいずれかを使用して、アプリケーションにアクセスできます。また、ブラウザを使用して同じエンドポイントURLにアクセスすることもできます。
-
保護されたリソースを匿名で攻撃してみましょう:
$ curl -i -X GET http://localhost:8080/api/public HTTP/1.1 200 OK Content-Length: 6 Content-Type: text/plain;charset=UTF-8 public%
-
保護されたリソースを匿名で攻撃してみましょう:
$ curl -i -X GET http://localhost:8080/api/admin HTTP/1.1 401 Unauthorized Content-Length: 14 Content-Type: text/html;charset=UTF-8 WWW-Authenticate: Basic Not authorized%
ブラウザーを使って保護されたリソースに匿名で接続すると、認証情報を入力するよう促すBasic認証フォームが表示されます。 |
-
認証されたユーザーとして保護されたエンドポイントに接続します:
$ curl -i -X GET -u admin:admin http://localhost:8080/api/admin
HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain;charset=UTF-8
admin%
3.5. 結果
許可されたユーザーの資格情報、例えば admin:admin
を提供すると、JPA セキュリティエクステンションはそのユーザーのロールを認証してロードします。 admin
ユーザーは保護されたリソースへのアクセスを許可されます。
リソースが @RolesAllowed("user")
で保護されている場合、次のシェルの例で説明するように、ユーザー admin
は "user" ロールに割り当てられていないため、リソースへのアクセスは許可されません:
$ curl -i -X GET -u admin:admin http://localhost:8080/api/users/me
HTTP/1.1 403 Forbidden
Content-Length: 34
Content-Type: text/html;charset=UTF-8
Forbidden%
最後に、ユーザ名 user
が認証され、セキュリティコンテキストには、例えばユーザ名などの主要な詳細が含まれます。
$ curl -i -X GET -u user:user http://localhost:8080/api/users/me
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/plain;charset=UTF-8
user%
Quarkus Security JPA information
セキュリティクイックスタートプロジェクトの実行とテストに成功したので、Quarkus SecurityとJPA IDストアのセキュリティ機能を理解していくための準備ができました。
サポートされているモデルの種類
-
@UserDefinition
クラスは JPA エンティティーである必要があります(Panache を使用しているかどうかは問いません)。 -
@Username
と@Password
フィールドの型はString
でなければなりません。 -
@Roles
フィールドはString
かCollection<String>
のいずれかのタイプであるか、またはCollection<X>
である必要があります。X
はエンティティークラスで、@RolesValue
アノテーションが付与されたString
フィールドが 1 つあります。 -
各
String
role 要素の型は、カンマで区切られたロールのリストとして解析されます。
別のエンティティーにロールを格納する
次のサンプルを使用して、別のエンティティ内にロールを格納します:
@UserDefinition
@Table(name = "test_user")
@Entity
public class User extends PanacheEntity {
@Username
public String name;
@Password
public String pass;
@ManyToMany
@Roles
public List<Role> roles = new ArrayList<>();
}
@Entity
public class Role extends PanacheEntity {
@ManyToMany(mappedBy = "roles")
public List<ExternalRolesUserEntity> users;
@RolesValue
public String role;
}
パスワードの保存とハッシュ化
デフォルトでは、Modular Crypt Format (MCF) のもと、bcrypt を用いてパスワードの保存とハッシュ化が行われます。
このようなハッシュ化されたパスワードを作成する必要がある場合は、便利な String BcryptUtil.bcryptHash(String password)
関数を用意しています。デフォルトでは、ランダムなソルトを作成して 10 回の繰り返しでハッシュ化します (繰り返しとソルトを指定することもできます)。
MCFを使う場合、ハッシュアルゴリズム、反復回数、ソルトはすべてハッシュ値に格納されるため、専用のカラムは必要ありません。 |
また、例えば @Password(value = PasswordType.CUSTOM, provider = CustomPasswordProvider.class)
といった形で別のハッシュアルゴリズムを使ってパスワードを保存することもできます:
@UserDefinition
@Table(name = "test_user")
@Entity
public class CustomPasswordUserEntity {
@Id
@GeneratedValue
public Long id;
@Column(name = "username")
@Username
public String name;
@Column(name = "password")
@Password(value = PasswordType.CUSTOM, provider = CustomPasswordProvider.class)
public String pass;
@Roles
public String role;
}
public class CustomPasswordProvider implements PasswordProvider {
@Override
public Password getPassword(String pass) {
byte[] digest = DatatypeConverter.parseHexBinary(pass);
return SimpleDigestPassword.createRaw(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_256, digest);
}
}
警告: テスト環境などでは @Password(PasswordType.CLEAR) を使ってパスワードをクリアテキストで保存することもできますが、本番では絶対にしないことを強くお勧めします。
|
次のステップ
Congratulations! You have learned how to create and test a secure Quarkus application by combining the Quarkus built-in basic HTTP authentication with the JPA identity provider.
このチュートリアルを完了したら、Quarkus のより高度なセキュリティメカニズムをいくつか検討してください。 次の情報を使用して、 OpenID Connect
を安全に使用して、Quarkusのエンドポイントに安全なシングルサインオンアクセスを提供する方法について学びます: