编辑此页面

使用 JDBC 进行安全

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

先决条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

  • 已安装 JDK 17+ 并正确配置了 JAVA_HOME

  • Apache Maven 3.9.9

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

架构

在本例中,我们构建了一个非常简单的微服务,它提供了三个端点:

  • /api/public

  • /api/users/me

  • /api/admin

/api/public 端点可以匿名访问。/api/admin 端点受 RBAC(基于角色的访问控制)保护,只有被授予 admin 角色的用户才能访问。在该端点,我们使用 @RolesAllowed 注解来声明式地强制执行访问约束。/api/users/me 端点也受 RBAC(基于角色的访问控制)保护,只有被授予 user 角色的用户才能访问。作为响应,它会返回一个包含用户详细信息的 JSON 文档。

解决方案

我们建议您按照以下章节中的说明,逐步创建应用程序。但是,您可以直接转到完整的示例。

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

解决方案位于 security-jdbc-quickstart 目录中。

创建 Maven 项目

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

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

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

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

要创建 Gradle 项目,请添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

对于 Windows 用户

  • 如果使用 cmd,(不要使用反斜杠 \ 并将所有内容放在同一行上)

  • 如果使用 Powershell,请将 -D 参数括在双引号中,例如 "-DprojectArtifactId=security-jdbc-quickstart"

别忘了添加您选择的数据库连接器库。这里我们使用 PostgreSQL 作为身份存储。

此命令生成一个新项目,导入 elytron-security-jdbc 扩展,该扩展是 Quarkus 应用程序的 wildfly-elytron-realm-jdbc 适配器。

如果您已经配置了 Quarkus 项目,可以通过在项目根目录中运行以下命令将 elytron-security-jdbc 扩展添加到您的项目中:

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")

编写应用程序

让我们从实现 /api/public 端点开始。从下面的源代码中可以看到,它只是一个普通的 Jakarta REST 资源

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";
    }
}

最后,我们来看一下 /api/users/me 端点。从下面的源代码可以看到,我们只信任具有 user 角色的用户。我们使用 SecurityContext 来访问当前已认证的主体,并返回用户的姓名。此信息是从数据库加载的。

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();
    }
}

配置应用程序

elytron-security-jdbc 扩展至少需要一个数据源来访问您的数据库。

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

在我们的场景中,我们使用 PostgreSQL 作为身份存储,并使用用户和角色初始化数据库。在此示例中,我们将使用加盐和哈希处理后的 password 版本作为密码。我们可以使用 BcryptUtil 类生成模块化加密格式 (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');

在注册新用户时,我们可以按如下方式加密他们的密码:

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
    }
}

我们现在可以配置 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

elytron-security-jdbc 扩展需要至少一个主体查询来认证用户及其身份。

1 我们定义了一个参数化 SQL 语句(带有 1 个参数),该语句应返回用户的密码以及您想要加载的任何附加信息。
2 密码映射器使用 SELECT 字段中密码字段的位置进行配置。哈希以模块化加密格式 (MCF) 存储,因为盐和迭代次数索引默认设置为 -1。您可以覆盖它们以将每个元素分解为三个单独的列。
3 我们使用 attribute-mappingsSELECT 投影字段(在此处为 u.role)绑定到目标主体表示属性。

principal-query 配置中,所有 index 属性都从 1 开始(而不是 0)。

测试应用程序

应用程序现在受到保护,并且身份由我们的数据库提供。首先要检查的是确保匿名访问正常。

$ curl -i -X GET https://:8080/api/public
HTTP/1.1 200 OK
Content-Length: 6
Content-Type: text/plain;charset=UTF-8

public%

现在,让我们尝试匿名访问一个受保护的资源。

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

Not authorized%

到目前为止一切顺利,现在让我们尝试使用允许的用户。

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

admin%

通过提供 admin:password 凭据,扩展会认证用户并加载其角色。admin 用户有权访问受保护的资源。

用户 admin 应该被禁止访问使用 @RolesAllowed("user") 保护的资源,因为它没有这个角色。

$ curl -i -X GET -u admin:password https://: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:password https://:8080/api/users/me
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/plain;charset=UTF-8

user%

高级配置

本指南仅涵盖了一个简单的用例,该扩展提供了多个数据源、多个主体查询配置以及 bcrypt 密码映射器。

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

配置参考

构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖

配置属性

类型

默认

Realm 名称

环境变量:QUARKUS_SECURITY_JDBC_REALM_NAME

显示更多

字符串

Quarkus

如果属性存储已启用。

环境变量:QUARKUS_SECURITY_JDBC_ENABLED

显示更多

布尔值

false

查找密码的 SQL 查询

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_SQL

显示更多

字符串

要使用的数据源

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_DATASOURCE

显示更多

字符串

如果启用了 clear-password-mapper。

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_CLEAR_PASSWORD_MAPPER_ENABLED

显示更多

布尔值

false

包含明文密码的列的索引(从 1 开始编号)

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_CLEAR_PASSWORD_MAPPER_PASSWORD_INDEX

显示更多

整数

1

如果启用了 bcrypt-password-mapper。

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_ENABLED

显示更多

布尔值

false

包含密码哈希的列的索引(从 1 开始编号)

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_PASSWORD_INDEX

显示更多

整数

0

引用密码哈希编码的字符串("BASE64" 或 "HEX")

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_HASH_ENCODING

显示更多

base64, hex

base64

包含 Bcrypt 盐的列的索引(从 1 开始编号)。默认值 -1 表示盐是使用模块化加密格式 (MCF) 标准存储在密码列中的。

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_SALT_INDEX

显示更多

整数

-1

引用盐编码的字符串("BASE64" 或 "HEX")

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_SALT_ENCODING

显示更多

base64, hex

base64

包含 Bcrypt 迭代次数的列的索引(从 1 开始编号)。默认值 -1 表示迭代次数是使用模块化加密格式 (MCF) 标准存储在密码列中的。

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_BCRYPT_PASSWORD_MAPPER_ITERATION_COUNT_INDEX

显示更多

整数

-1

要映射的列的索引(从 1 开始编号)

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__INDEX

显示更多

整数

0

目标属性名称

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__TO

显示更多

字符串

必需

命名查询

类型

默认

查找密码的 SQL 查询

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__SQL

显示更多

字符串

要使用的数据源

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__DATASOURCE

显示更多

字符串

要映射的列的索引(从 1 开始编号)

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__INDEX

显示更多

整数

0

目标属性名称

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__TO

显示更多

字符串

必需

如果启用了 clear-password-mapper。

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__CLEAR_PASSWORD_MAPPER_ENABLED

显示更多

布尔值

false

包含明文密码的列的索引(从 1 开始编号)

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__CLEAR_PASSWORD_MAPPER_PASSWORD_INDEX

显示更多

整数

1

如果启用了 bcrypt-password-mapper。

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_ENABLED

显示更多

布尔值

false

包含密码哈希的列的索引(从 1 开始编号)

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_PASSWORD_INDEX

显示更多

整数

0

引用密码哈希编码的字符串("BASE64" 或 "HEX")

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_HASH_ENCODING

显示更多

base64, hex

base64

包含 Bcrypt 盐的列的索引(从 1 开始编号)。默认值 -1 表示盐是使用模块化加密格式 (MCF) 标准存储在密码列中的。

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_SALT_INDEX

显示更多

整数

-1

引用盐编码的字符串("BASE64" 或 "HEX")

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_SALT_ENCODING

显示更多

base64, hex

base64

包含 Bcrypt 迭代次数的列的索引(从 1 开始编号)。默认值 -1 表示迭代次数是使用模块化加密格式 (MCF) 标准存储在密码列中的。

环境变量:QUARKUS_SECURITY_JDBC_PRINCIPAL_QUERY__QUERY_NAME__BCRYPT_PASSWORD_MAPPER_ITERATION_COUNT_INDEX

显示更多

整数

-1

相关内容