Using Security with JDBC

本指南演示 Quarkus 应用如何使用数据库来存储您的用户标识。

Prerequisites

Unresolved directive in security-jdbc.adoc - include::{includes}/prerequisites.adoc[]

Architecture

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

  • /api/public

  • /api/users/me

  • /api/admin

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

Solution

我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。

克隆 Git 存储库: git clone {quickstarts-clone-url},或下载 {quickstarts-archive-url}[存档]。

该解决方案位于 security-jdbc-quickstart directory 中。

Creating the Maven Project

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

Unresolved directive in security-jdbc.adoc - include::{includes}/devtools/create-app.adoc[]

别忘了添加数据库连接器库。我们在此使用 PostgreSQL 作为身份存储。

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

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

Unresolved directive in security-jdbc.adoc - include::{includes}/devtools/extension-add.adoc[]

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

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

Writing the application

首先,实现 /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();
    }
}

Configuring the Application

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 个参数),它应返回用户的密码以及您要加载的任何其他信息。
<1>  密码映射器配置为 `SELECT` 字段中密码字段的位置。哈希存储在模块加密格式 (MCF) 中,因为盐和迭代计数索引默认设置为 `-1`。您可以覆盖它们,以便将每个元素分解为三个单独的列。
<1>  我们使用 `attribute-mappings` 将 `SELECT` 投影字段(此处为 `u.role`)绑定到目标主体表示属性。

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

Testing the Application

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

$ 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

Not authorized%

目前为止还不错,现在我们尝试使用允许的用户。

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

通过提供 admin:password 凭据,该扩展对用户进行身份验证并加载其角色。 admin 用户被授权访问受保护的资源。

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

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

最后,使用用户 user 有效,并且安全上下文包含主体详细信息(例如用户名)。

$ 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

本指南仅涵盖了一个简单的用例,该扩展提供了多个数据源、多个主体查询配置以及一个 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

Configuration Reference

Unresolved directive in security-jdbc.adoc - include::{generated-dir}/config/quarkus-elytron-security-jdbc.adoc[]