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

使用JDBC的安全性

本指南展示了你的Quarkus应用程序如何使用数据库来存储你的用户身份。

先决条件

完成这个指南,你需要:

  • 大概15分钟

  • 编辑器

  • JDK 17+ installed with JAVA_HOME configured appropriately

  • Apache Maven 3.9.9

  • 如果你愿意的话,还可以选择使用Quarkus CLI

  • 如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)

应用结构

In this example, we build a very simple microservice which offers three endpoints:

  • /api/public

  • /api/users/me

  • /api/admin

The /api/public endpoint can be accessed anonymously. The /api/admin endpoint is protected with RBAC (Role-Based Access Control) where only users granted with the admin role can access. At this endpoint, we use the @RolesAllowed annotation to declaratively enforce the access constraint. The /api/users/me endpoint is also protected with RBAC (Role-Based Access Control) where only users granted with the user role can access. As a response, it returns a JSON document with details about the user.

解决方案

我们建议您按照下一节的说明逐步创建应用程序。然而,您可以直接转到已完成的示例。

克隆 Git 仓库: git clone https://github.com/quarkusio/quarkus-quickstarts.git ,或下载一个 存档

The solution is located in the security-jdbc-quickstart directory.

创建Maven项目

首先,我们需要一个新的工程项目。用以下命令创建一个新项目:

CLI
quarkus create app org.acme:security-jdbc-quickstart \
    --extension='elytron-security-jdbc,jdbc-postgresql,rest' \
    --no-code
cd security-jdbc-quickstart

创建Grade项目,请添加 --gradle 或者 --gradle-kotlin-dsl 参数。

For more information about how to install and use the Quarkus CLI, see the Quarkus CLI guide.

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.16.3:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=security-jdbc-quickstart \
    -Dextensions='elytron-security-jdbc,jdbc-postgresql,rest' \
    -DnoCode
cd security-jdbc-quickstart

创建Grade项目,请添加 -DbuildTool=gradle 或者 -DbuildTool=gradle-kotlin-dsl 参数。

For Windows users:

  • If using cmd, (don’t use backward slash \ and put everything on the same line)

  • If using Powershell, wrap -D parameters in double quotes e.g. "-DprojectArtifactId=security-jdbc-quickstart"

Don’t forget to add the database connector library of choice. Here we are using PostgreSQL as identity store.

This command generates a new project, importing the elytron-security-jdbc extension which is an wildfly-elytron-realm-jdbc adapter for Quarkus applications.

If you already have your Quarkus project configured, you can add the elytron-security-jdbc extension to your project by running the following command in your project base directory:

CLI
quarkus extension add elytron-security-jdbc
Maven
./mvnw quarkus:add-extension -Dextensions='elytron-security-jdbc'
Gradle
./gradlew addExtension --extensions='elytron-security-jdbc'

这会将以下内容添加到你的构建文件中:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-elytron-security-jdbc</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-elytron-security-jdbc")

编写应用程序

Let’s start by implementing the /api/public endpoint. As you can see from the source code below, it is just a regular Jakarta REST resource:

package org.acme.security.jdbc;

import jakarta.annotation.security.PermitAll;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.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 角色的用户才能访问该端点:

package org.acme.security.jdbc;

import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/api/admin")
public class AdminResource {

    @GET
    @RolesAllowed("admin")
    @Produces(MediaType.TEXT_PLAIN)
    public String adminResource() {
         return "admin";
    }
}

Finally, let’s consider the /api/users/me endpoint. As you can see from the source code below, we are trusting only users with the user role. We are using SecurityContext to get access to the current authenticated Principal, and we return the user’s name. This information is loaded from the database.

package org.acme.security.jdbc;

import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;
import jakarta.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();
    }
}

Configuring the Application

The elytron-security-jdbc extension requires at least one datasource to access to your database.

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.datasource.jdbc.url=jdbc:postgresql:elytron-security-jdbc

In our context, we are using PostgreSQL as identity store, and we initialize the database with users and roles. We will use the salted and hashed version of password as a password in this example. We can use the BcryptUtil class to generate passwords in the Modular Crypt Format (MCF).

CREATE TABLE test_user (
  id INT,
  username VARCHAR(255),
  password VARCHAR(255),
  role VARCHAR(255)
);

INSERT INTO test_user (id, username, password, role) VALUES (1, 'admin', '$2a$10$Uc.SZ0hvGJQlYdsAp7be1.lFjmOnc7aAr4L0YY3/VN3oK.F8zJHRG', 'admin');
INSERT INTO test_user (id, username, password, role) VALUES (2, 'user','$2a$10$Uc.SZ0hvGJQlYdsAp7be1.lFjmOnc7aAr4L0YY3/VN3oK.F8zJHRG', 'user');

When signing up new users, we can encrypt their password as follows:

package org.acme.security.jdbc;

import io.quarkus.elytron.security.common.BcryptUtil;

public class AccountService {

    public void signupUser(String username, String password) {
        String encryptedPassword = BcryptUtil.bcryptHash(password);

        // store user with the encrypted password in the database
    }
}

We can now configure the Elytron JDBC Realm.

quarkus.security.jdbc.enabled=true
quarkus.security.jdbc.principal-query.sql=SELECT u.password, u.role FROM test_user u WHERE u.username=? (1)
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.enabled=true (2)
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.password-index=1
quarkus.security.jdbc.principal-query.attribute-mappings.0.index=2 (3)
quarkus.security.jdbc.principal-query.attribute-mappings.0.to=groups

The elytron-security-jdbc extension requires at least one principal query to authenticate the user and its identity.

1 We define a parameterized SQL statement (with exactly 1 parameter) which should return the user’s password plus any additional information you want to load.
2 The password mapper is configured with the position of the password field in the SELECT fields. The hash is stored in the Modular Crypt Format (MCF) because the salt and iteration count indexes are set to -1 by default. You can override them in order to decompose each element into three separate columns.
3 We use attribute-mappings to bind the SELECT projection fields (i.e. u.role here) to the target Principal representation attributes.

In the principal-query configuration all the index properties start at 1 (rather than 0).

测试应用程序

The application is now protected and the identities are provided by our database. The very first thing to check is to ensure the anonymous access works.

$ 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%

Now, let’s try to hit a protected resource anonymously.

$ curl -i -X GET http://localhost:8080/api/admin
HTTP/1.1 401 Unauthorized
Content-Length: 14
Content-Type: text/html;charset=UTF-8

Not authorized%

So far so good, now let’s try with an allowed user.

$ curl -i -X GET -u admin:password http://localhost:8080/api/admin
HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain;charset=UTF-8

admin%

By providing the admin:password credentials, the extension authenticated the user and loaded their roles. The admin user is authorized to access to the protected resources.

The user admin should be forbidden to access a resource protected with @RolesAllowed("user") because it doesn’t have this role.

$ curl -i -X GET -u admin:password http://localhost:8080/api/users/me
HTTP/1.1 403 Forbidden
Content-Length: 34
Content-Type: text/html;charset=UTF-8

Forbidden%

Finally, using the user user works and the security context contains the principal details (username for instance).

$ curl -i -X GET -u user:password http://localhost:8080/api/users/me
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/plain;charset=UTF-8

user%

Advanced Configuration

This guide only covered an easy use case, the extension offers multiple datasources, multiple principal queries configuration as well as a bcrypt password mapper.

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.datasource.jdbc.url=jdbc:postgresql:multiple-data-sources-users

quarkus.datasource.permissions.db-kind=postgresql
quarkus.datasource.permissions.username=quarkus
quarkus.datasource.permissions.password=quarkus
quarkus.datasource.permissions.jdbc.url=jdbc:postgresql:multiple-data-sources-permissions

quarkus.security.jdbc.enabled=true
quarkus.security.jdbc.principal-query.sql=SELECT u.password FROM test_user u WHERE u.username=?
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.enabled=true
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.password-index=1

quarkus.security.jdbc.principal-query.roles.sql=SELECT r.role_name FROM test_role r, test_user_role ur WHERE ur.username=? AND ur.role_id = r.id
quarkus.security.jdbc.principal-query.roles.datasource=permissions
quarkus.security.jdbc.principal-query.roles.attribute-mappings.0.index=1
quarkus.security.jdbc.principal-query.roles.attribute-mappings.0.to=groups

配置参考

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

类型

默认

The realm name

Environment variable: QUARKUS_SECURITY_JDBC_REALM_NAME

Show more

string

Quarkus

If the properties store is enabled.

Environment variable: QUARKUS_SECURITY_JDBC_ENABLED

Show more

boolean

false

The sql query to find the password

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_SQL

Show more

string

The data source to use

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_DATASOURCE

Show more

string

If the clear-password-mapper is enabled.

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_CLEAR_PASSWORD_MAPPER_ENABLED

Show more

boolean

false

The index (1 based numbering) of the column containing the clear password

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_CLEAR_PASSWORD_MAPPER_PASSWORD_INDEX

Show more

int

1

If the bcrypt-password-mapper is enabled.

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_ENABLED

Show more

boolean

false

The index (1 based numbering) of the column containing the password hash

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_PASSWORD_INDEX

Show more

int

0

A string referencing the password hash encoding ("BASE64" or "HEX")

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_HASH_ENCODING

Show more

base64, hex

base64

The index (1 based numbering) of the column containing the Bcrypt salt. The default value of -1 implies that the salt is stored in the password column using the Modular Crypt Format (MCF) standard.

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_SALT_INDEX

Show more

int

-1

A string referencing the salt encoding ("BASE64" or "HEX")

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_SALT_ENCODING

Show more

base64, hex

base64

The index (1 based numbering) of the column containing the Bcrypt iteration count. The default value of -1 implies that the iteration count is stored in the password column using the Modular Crypt Format (MCF) standard.

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_ITERATION_COUNT_INDEX

Show more

int

-1

The index (1 based numbering) of column to map

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__INDEX

Show more

int

0

The target attribute name

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__TO

Show more

string

required

Named queries

类型

默认

The sql query to find the password

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__SQL

Show more

string

The data source to use

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__DATASOURCE

Show more

string

The index (1 based numbering) of column to map

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__INDEX

Show more

int

0

The target attribute name

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__TO

Show more

string

required

If the clear-password-mapper is enabled.

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__CLEAR_PASSWORD_MAPPER_ENABLED

Show more

boolean

false

The index (1 based numbering) of the column containing the clear password

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__CLEAR_PASSWORD_MAPPER_PASSWORD_INDEX

Show more

int

1

If the bcrypt-password-mapper is enabled.

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_ENABLED

Show more

boolean

false

The index (1 based numbering) of the column containing the password hash

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_PASSWORD_INDEX

Show more

int

0

A string referencing the password hash encoding ("BASE64" or "HEX")

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_HASH_ENCODING

Show more

base64, hex

base64

The index (1 based numbering) of the column containing the Bcrypt salt. The default value of -1 implies that the salt is stored in the password column using the Modular Crypt Format (MCF) standard.

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_SALT_INDEX

Show more

int

-1

A string referencing the salt encoding ("BASE64" or "HEX")

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_SALT_ENCODING

Show more

base64, hex

base64

The index (1 based numbering) of the column containing the Bcrypt iteration count. The default value of -1 implies that the iteration count is stored in the password column using the Modular Crypt Format (MCF) standard.

Environment variable: QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_ITERATION_COUNT_INDEX

Show more

int

-1

Related content