Usando a segurança com JDBC
Este guia demonstra como seu aplicativo Quarkus pode usar um banco de dados para armazenar suas identidades de usuário.
Pré-requisitos
Para concluir este guia, você precisa:
-
Cerca de 15 minutos
-
Um IDE
-
JDK 17+ installed with
JAVA_HOME
configured appropriately -
Apache Maven 3.9.9
-
Opcionalmente, o Quarkus CLI se você quiser usá-lo
-
Opcionalmente, Mandrel ou GraalVM instalado e configurado apropriadamente se você quiser criar um executável nativo (ou Docker se você usar uma compilação de contêiner nativo)
Arquitetura
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.
Solução
Recomendamos que siga as instruções nas seções seguintes e crie a aplicação passo a passo. No entanto, você pode ir diretamente para o exemplo completo.
Clone o repositório Git: git clone https://github.com/quarkusio/quarkus-quickstarts.git
, ou baixe um arquivo.
The solution is located in the security-jdbc-quickstart
directory.
Criando o projeto Maven
Primeiro, precisamos de um novo projeto. Crie um novo projeto com o seguinte comando:
Para usuários do Windows:
-
Se estiver usando cmd, (não use barra invertida '\' e coloque tudo na mesma linha)
-
Se estiver usando o Powershell, envolva os parâmetros '-D' entre aspas duplas, por exemplo, '"-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:
quarkus extension add elytron-security-jdbc
./mvnw quarkus:add-extension -Dextensions='elytron-security-jdbc'
./gradlew addExtension --extensions='elytron-security-jdbc'
Isto irá adicionar o seguinte trecho no seu arquivo de build:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-jdbc</artifactId>
</dependency>
implementation("io.quarkus:quarkus-elytron-security-jdbc")
Writing the application
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";
}
}
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";
}
}
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 |
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%
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
Referência de configuração
Propriedade de Configuração Fixa no Momento da Compilação - Todas as outras propriedades de configuração podem ser sobrepostas em tempo de execução.
Configuration property |
Tipo |
Padrão |
---|---|---|
The realm name Environment variable: Show more |
string |
|
If the properties store is enabled. Environment variable: Show more |
boolean |
|
The sql query to find the password Environment variable: Show more |
string |
|
The data source to use Environment variable: Show more |
string |
|
If the clear-password-mapper is enabled. Environment variable: Show more |
boolean |
|
The index (1 based numbering) of the column containing the clear password Environment variable: Show more |
int |
|
If the bcrypt-password-mapper is enabled. Environment variable: Show more |
boolean |
|
The index (1 based numbering) of the column containing the password hash Environment variable: Show more |
int |
|
A string referencing the password hash encoding ("BASE64" or "HEX") Environment variable: Show more |
|
|
The index (1 based numbering) of the column containing the Bcrypt salt. The default value of Environment variable: Show more |
int |
|
A string referencing the salt encoding ("BASE64" or "HEX") Environment variable: Show more |
|
|
The index (1 based numbering) of the column containing the Bcrypt iteration count. The default value of Environment variable: Show more |
int |
|
The index (1 based numbering) of column to map Environment variable: Show more |
int |
|
The target attribute name Environment variable: Show more |
string |
required |
Tipo |
Padrão |
|
The sql query to find the password Environment variable: Show more |
string |
|
The data source to use Environment variable: Show more |
string |
|
The index (1 based numbering) of column to map Environment variable: Show more |
int |
|
The target attribute name Environment variable: Show more |
string |
required |
If the clear-password-mapper is enabled. Environment variable: Show more |
boolean |
|
The index (1 based numbering) of the column containing the clear password Environment variable: Show more |
int |
|
If the bcrypt-password-mapper is enabled. Environment variable: Show more |
boolean |
|
The index (1 based numbering) of the column containing the password hash Environment variable: Show more |
int |
|
A string referencing the password hash encoding ("BASE64" or "HEX") Environment variable: Show more |
|
|
The index (1 based numbering) of the column containing the Bcrypt salt. The default value of Environment variable: Show more |
int |
|
A string referencing the salt encoding ("BASE64" or "HEX") Environment variable: Show more |
|
|
The index (1 based numbering) of the column containing the Bcrypt iteration count. The default value of Environment variable: Show more |
int |
|