Secure a Quarkus application with Basic authentication and Jakarta Persistence
Secure your Quarkus application endpoints by combining the built-in Quarkus Basic authentication with the Jakarta Persistence identity provider to enable role-based access control (RBAC).
The Jakarta Persistence IdentityProvider creates a SecurityIdentity instance, which is used during user authentication to verify and authorize access requests making your Quarkus application secure.
Para obter mais informações sobre o Jakarta Persistence, consulte o guia Quarkus Security com Jakarta Persistence .
Este tutorial prepara você para implementar mecanismos de segurança mais avançados no Quarkus, por exemplo, como usar o mecanismo de autenticação OpenID Connect (OIDC).
Pré-requisitos
Para concluir este guia, você precisa:
-
Cerca de 15 minutos
-
Um IDE
-
JDK 11+ instalado com 'JAVA_HOME' configurado adequadamente
-
Apache Maven 3.9.3
-
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)
O que você vai construir
Para demonstrar diferentes políticas de autorização, as etapas deste tutorial orientam você na criação de uma aplicação que fornece os seguintes endpoints:
| Endpoint | Descrição |
|---|---|
|
O endpoint |
|
O endpoint |
|
O endpoint |
1. Crie e verifique o projeto Maven
Para que o Quarkus Security possa mapear sua fonte de segurança para as entidades do Jakarta Persistence, certifique-se de que o projeto Maven usado neste tutorial inclua a extensão security-jpa ou security-jpa-reactive .
|
Hibernate ORM com Panache é usado para armazenar as identidades dos usuários, mas também é possível usar Hibernate ORM com a extensão Você também deve adicionar sua biblioteca de conector de banco de dados preferida. As instruções neste tutorial de exemplo usam um banco de dados PostgreSQL para o armazenamento de identidade. |
1.1. Crie o projeto Maven
Você pode criar um novo projeto Maven com a extensão Security Jakarta Persistence ou pode adicionar a extensão a um projeto Maven existente. Você pode usar o Hibernate ORM ou o Hibernate Reativo.
-
Para criar um novo projeto Maven com a extensão Jakarta Persistence, conclua uma das etapas a seguir:
-
Para criar o projeto Maven com o Hibernate ORM, use o seguinte comando:
-
Para criar o projeto Maven com o Hibernate Reativo, use o seguinte comando:
-
-
Para adicionar a extensão Jakarta Persistence a um projeto Maven existente, conclua uma das etapas a seguir:
-
Para adicionar a extensão Security Jakarta Persistence a um projeto Maven existente com o Hibernate ORM, execute o seguinte comando no diretório base do projeto:
CLIquarkus extension add 'security-jpa'Maven./mvnw quarkus:add-extension -Dextensions='security-jpa'Gradle./gradlew addExtension --extensions='security-jpa' -
Para adicionar a extensão Security Jakarta Persistence a um projeto Maven existente com o Hibernate Reativo, execute o seguinte comando no diretório base do projeto:
CLIquarkus extension add 'security-jpa-reactive'Maven./mvnw quarkus:add-extension -Dextensions='security-jpa-reactive'Gradle./gradlew addExtension --extensions='security-jpa-reactive'
-
1.2. Verifique a dependência do quarkus-security-jpa
Depois de executar um dos comandos anteriores para criar o projeto Maven, verifique se a dependência security-jpa foi adicionada ao arquivo XML de construção do projeto.
-
Para verificar a extensão
security-jpa, verifique a seguinte configuração:pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-security-jpa</artifactId> </dependency>build.gradleimplementation("io.quarkus:quarkus-security-jpa") -
Para verificar a extensão
security-jpa-reactive, verifique a seguinte configuração:pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-security-jpa-reactive</artifactId> </dependency>build.gradleimplementation("io.quarkus:quarkus-security-jpa-reactive")
2. Escreva a aplicação
-
Proteja o endpoint da API para determinar quem pode acessar a aplicação usando uma das seguintes abordagens:
-
Implemente o endpoint
/api/publicpara permitir que todos os usuários acessem a aplicação. Adicione um recurso Jakarta REST regular ao seu código-fonte Java, conforme mostrado no trecho de código a seguir:package org.acme.security.jpa; 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"; } } -
Implemente o endpoint
/api/publicpara permitir que todos os usuários acessem a aplicação. O código-fonte do endpoint/api/adminé semelhante, mas, em vez disso, você usa uma anotação@RolesAllowedpara garantir que somente os usuários aos quais foi concedida a funçãoadminpossam acessar o endpoint. Adicione um recurso Jakarta REST com a seguinte anotação@RolesAllowed:package org.acme.security.jpa; 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"; } } -
Implemente um endpoint
/api/users/meque só possa ser acessado por usuários que tenham a funçãouser. UseSecurityContextpara obter acesso ao usuárioPrincipalautenticado no momento e para retornar o nome de usuário dele, que é recuperado do banco de dados.package org.acme.security.jpa; 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(); } }
-
2.1. Defina a entidade do usuário
-
Agora, você pode descrever como deseja que as informações de segurança sejam armazenadas no modelo, adicionando anotações à entidade
user, conforme descrito no seguinte trecho de código:
package org.acme.security.jpa;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import io.quarkus.elytron.security.common.BcryptUtil;
import io.quarkus.security.jpa.Password;
import io.quarkus.security.jpa.Roles;
import io.quarkus.security.jpa.UserDefinition;
import io.quarkus.security.jpa.Username;
@Entity
@Table(name = "test_user")
@UserDefinition (1)
public class User extends PanacheEntity {
@Username (2)
public String username;
@Password (3)
public String password;
@Roles (4)
public String role;
/**
* Adds a new user to the database
* @param username the username
* @param password the unencrypted password (it will be encrypted with bcrypt)
* @param role the comma-separated roles
*/
public static void add(String username, String password, String role) { (5)
User user = new User();
user.username = username;
user.password = BcryptUtil.bcryptHash(password);
user.role = role;
user.persist();
}
}
A extensão security-jpa é inicializada somente se uma única entidade for anotada com @UserDefinition.
| 1 | A anotação @UserDefinition deve estar presente em uma única entidade e pode ser uma entidade Hibernate ORM comum ou uma entidade Hibernate ORM com Panache. |
| 2 | Indica o campo usado para o nome de usuário. |
| 3 | Indica o campo usado para a senha. Por padrão, ele usa senhas com hash bcrypt. Você pode configurá-lo para usar texto em claro ou senhas personalizadas. |
| 4 | Indica a lista separada por vírgulas de funções adicionadas aos atributos de representação do principal de destino. |
| 5 | Permite-nos adicionar usuários enquanto fazemos o hash das senhas com o hash bcrypt adequado. |
Hibernate Reactive Panache uses io.quarkus.hibernate.reactive.panache.PanacheEntity instead of io.quarkus.hibernate.orm.panache.PanacheEntity.
For more information, see User file.
|
2.2. Configure a aplicação
-
Ative o mecanismo de autenticação básico do Quarkus integrado definindo a propriedade
quarkus.http.auth.basiccomotrue:quarkus.http.auth.basic=true`Quando o acesso seguro é necessário e nenhum outro mecanismo de autenticação está ativado, a autenticação básica integrada do Quarkus é o mecanismo de autenticação de reserva. Portanto, neste tutorial, você não precisa definir a propriedade
quarkus.http.auth.basiccomotrue. -
Configure pelo menos uma fonte de dados no arquivo
application.propertiespara que a extensãosecurity-jpapossa acessar seu banco de dados. Por exemplo:quarkus.http.auth.basic=true quarkus.datasource.db-kind=postgresql quarkus.datasource.username=quarkus quarkus.datasource.password=quarkus quarkus.datasource.jdbc.url=jdbc:postgresql:security_jpa quarkus.hibernate-orm.database.generation=drop-and-create -
Para inicializar o banco de dados com usuários e funções, implemente a classe
Startup, conforme descrito no trecho de código a seguir:
|
package org.acme.security.jpa;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;
import jakarta.transaction.Transactional;
import io.quarkus.runtime.StartupEvent;
@Singleton
public class Startup {
@Transactional
public void loadUsers(@Observes StartupEvent evt) {
// reset and load all test users
User.deleteAll();
User.add("admin", "admin", "admin");
User.add("user", "user", "user");
}
}
O exemplo anterior demonstra como a aplicação pode ser protegida e as identidades fornecidas pelo banco de dados especificado.
|
Em um ambiente de produção, não armazene senhas em claro. Como resultado, o |
3. Teste sua aplicação usando o Dev Services para PostgreSQL
Conclua o teste de integração da sua aplicação nos modos JVM e nativo usando o Dev Services para PostgreSQL antes de executar a aplicação no modo de produção.
-
Para executar sua aplicação no modo de desenvolvimento:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
-
A configuração de propriedades a seguir demonstra como você pode permitir que o teste do PostgreSQL seja executado somente no modo de produção (
prod). Nesse cenário, oDev Services para PostgreSQLinicia e configura um contêiner de testePostgreSQL.
%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.username=quarkus
%prod.quarkus.datasource.password=quarkus
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql:elytron_security_jpa
quarkus.hibernate-orm.database.generation=drop-and-create
-
Se você adicionar o prefixo de perfil
%prod., as propriedades da fonte de dados não estarão visíveis emDev Services para PostgreSQLe só serão observadas por uma aplicação em execução no modo de produção. -
Para escrever o teste de integração, use o exemplo de código a seguir:
package org.acme.elytron.security.jpa;
import static io.restassured.RestAssured.get;
import static io.restassured.RestAssured.given;
import static org.hamcrest.core.Is.is;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class JpaSecurityRealmTest {
@Test
void shouldAccessPublicWhenAnonymous() {
get("/api/public")
.then()
.statusCode(HttpStatus.SC_OK);
}
@Test
void shouldNotAccessAdminWhenAnonymous() {
get("/api/admin")
.then()
.statusCode(HttpStatus.SC_UNAUTHORIZED);
}
@Test
void shouldAccessAdminWhenAdminAuthenticated() {
given()
.auth().preemptive().basic("admin", "admin")
.when()
.get("/api/admin")
.then()
.statusCode(HttpStatus.SC_OK);
}
@Test
void shouldNotAccessUserWhenAdminAuthenticated() {
given()
.auth().preemptive().basic("admin", "admin")
.when()
.get("/api/users/me")
.then()
.statusCode(HttpStatus.SC_FORBIDDEN);
}
@Test
void shouldAccessUserAndGetIdentityWhenUserAuthenticated() {
given()
.auth().preemptive().basic("user", "user")
.when()
.get("/api/users/me")
.then()
.statusCode(HttpStatus.SC_OK)
.body(is("user"));
}
}
Como você pode ver neste exemplo de código, não é necessário iniciar o contêiner de teste a partir do código de teste.
|
When you start your application in dev mode, |
3.1. Use o Curl ou um navegador para testar sua aplicação
-
Use o exemplo a seguir para iniciar o servidor PostgreSQL:
docker run --rm=true --name security-getting-started -e POSTGRES_USER=quarkus \
-e POSTGRES_PASSWORD=quarkus -e POSTGRES_DB=elytron_security_jpa \
-p 5432:5432 postgres:14.1
3.2. Compile e execute a aplicação
-
Compile e execute sua aplicação Quarkus usando um dos métodos a seguir:
-
Modo JVM
-
Compile a aplicação:
CLIquarkus buildMaven./mvnw installGradle./gradlew build -
Execute a aplicação:
java -jar target/quarkus-app/quarkus-run.jar
-
-
Modo nativo
-
Compile a aplicação:
CLIquarkus build --nativeMaven./mvnw install -DnativeGradle./gradlew build -Dquarkus.package.type=native -
Execute a aplicação:
./target/security-jpa-quickstart-runner
-
-
3.3. Acesse e teste a segurança da aplicação
Quando a aplicação está em execução, você pode acessar seus endpoints usando um dos seguintes comandos Curl.
-
Conecte-se a um endpoint protegido de forma anônima:
$ 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 -
Conecte-se a um endpoint protegido de forma anônima:
$ curl -i -X GET http://localhost:8080/api/admin HTTP/1.1 401 Unauthorized Content-Length: 14 Content-Type: text/html;charset=UTF-8 WWW-Authenticate: Basic Not authorized -
Conecte-se a um endpoint protegido como um usuário autorizado:
$ curl -i -X GET -u admin:admin http://localhost:8080/api/admin HTTP/1.1 200 OK Content-Length: 5 Content-Type: text/plain;charset=UTF-8 admin
Você também pode acessar os mesmos URLs de endpoint usando um navegador.
|
Se o usuário usar um navegador para se conectar anonimamente a um recurso protegido, será exibido um formulário de autenticação básica, solicitando que o usuário insira as credenciais. |
3.4. Resultados
Quando o usuário fornece as credenciais de um usuário autorizado, por exemplo, admin:admin , a extensão de segurança do Jakarta Persistence autentica e carrega as funções do usuário. O usuário admin está autorizado a acessar os recursos protegidos.
Se um recurso estiver protegido com @RolesAllowed("user"), o usuário admin não está autorizado a acessar o recurso porque não está atribuído à função "user", conforme mostrado no exemplo de shell a seguir:
$ curl -i -X GET -u admin:admin http://localhost:8080/api/users/me
HTTP/1.1 403 Forbidden
Content-Length: 34
Content-Type: text/html;charset=UTF-8
Forbidden
Por fim, o usuário chamado user é autorizado e o contexto de segurança contém os detalhes principais, por exemplo, o nome de usuário.
$ curl -i -X GET -u user:user http://localhost:8080/api/users/me
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/plain;charset=UTF-8
user
What’s next
Parabéns! Você aprendeu a criar e testar uma aplicação Quarkus segura combinando a autenticação básica integrada do Quarkus com o provedor de identidade Jakarta Persistence.
Depois de concluir este tutorial, você pode explorar mecanismos de segurança mais avançados no Quarkus. As informações a seguir mostram como usar o OpenID Connect para obter acesso seguro de login único aos endpoints do Quarkus: