Using Security with JDBC
本指南演示 Quarkus 应用如何使用数据库来存储您的用户标识。
This guide demonstrates how your Quarkus application can use a database to store your user identities.
Architecture
在此示例中,我们构建了一个非常简单的微服务,它提供三个端点:
In this example, we build a very simple microservice which offers three endpoints:
-
/api/public
-
/api/users/me
-
/api/admin
/api/public
端点可以匿名访问。/api/admin
端点受 RBAC(基于角色的访问控制)保护,只有被授予 admin
角色的用户才能访问。在此端点,我们使用 @RolesAllowed
注释来以声明方式强制访问约束。/api/users/me
端点也受 RBAC(基于角色的访问控制)保护,只有被授予 user
角色的用户才能访问。作为响应,它返回一个包含用户详细信息的 JSON 文档。
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.
Solution
我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
克隆 Git 存储库: git clone {quickstarts-clone-url}
,或下载 {quickstarts-archive-url}[存档]。
Clone the Git repository: git clone {quickstarts-clone-url}
, or download an {quickstarts-archive-url}[archive].
该解决方案位于 security-jdbc-quickstart
directory 中。
The solution is located in the security-jdbc-quickstart
directory.
Creating the Maven Project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
First, we need a new project. Create a new project with the following command:
Unresolved directive in security-jdbc.adoc - include::{includes}/devtools/create-app.adoc[]
别忘了添加数据库连接器库。我们在此使用 PostgreSQL 作为身份存储。 Don’t forget to add the database connector library of choice. Here we are using PostgreSQL as identity store. |
此命令生成一个新项目,引入 elytron-security-jdbc
扩展,该扩展是 Quarkus 应用的 wildfly-elytron-realm-jdbc
适配器。
This command generates a new project, importing the elytron-security-jdbc
extension
which is an wildfly-elytron-realm-jdbc
adapter for Quarkus applications.
如果您已配置好 Quarkus 项目,可以通过在项目基本目录中运行以下命令将 elytron-security-jdbc
扩展添加到您的项目:
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:
Unresolved directive in security-jdbc.adoc - include::{includes}/devtools/extension-add.adoc[]
这会将以下内容添加到构建文件中:
This will add the following to your build file:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-jdbc</artifactId>
</dependency>
implementation("io.quarkus:quarkus-elytron-security-jdbc")
Writing the application
首先,实现 /api/public
端点。正如您从下面的源代码中所见,它仅仅是一个常规的 Jakarta REST 资源:
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
角色的用户才可以访问此端点:
The source code for the /api/admin
endpoint is also very simple. The main difference here is that we are using a @RolesAllowed
annotation to make sure that only users granted with the admin
role can access the endpoint:
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
获取当前经过身份验证的主体,然后返回用户的名称。此信息从数据库中加载。
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
elytron-security-jdbc
扩展至少需要一个数据源来访问您的数据库。
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
在我们的上下文中,我们将 PostgreSQL 用作身份存储,并使用用户和角色初始化数据库。在此示例中,我们将 password
的加盐和哈希版本用作密码。我们可以使用 BcryptUtil
类以模块加密格式 (MCF) 生成密码。
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
}
}
我们现在可以配置 Elytron JDBC Realm。
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
elytron-security-jdbc
扩展至少需要一个主体查询来验证用户及其身份。
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 |
Testing the Application
应用程序现在受到保护,并且数据库提供了身份。首先要检查的是确保匿名访问有效。
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%
通过提供 admin:password
凭据,该扩展对用户进行身份验证并加载其角色。 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.
用户 admin
应禁止访问受 @RolesAllowed("user")
保护的资源,因为它没有这个角色。
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%
最后,使用用户 user
有效,并且安全上下文包含主体详细信息(例如用户名)。
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
本指南仅涵盖了一个简单的用例,该扩展提供了多个数据源、多个主体查询配置以及一个 bcrypt 密码映射器。
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 Reference
Unresolved directive in security-jdbc.adoc - include::{generated-dir}/config/quarkus-elytron-security-jdbc.adoc[]