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

/api/public

O endpoint /api/public pode ser acessado anonimamente.

/api/admin

O endpoint /api/admin é protegido com controle de acesso baseado em função (RBAC). Somente usuários com a função admin podem acessá-lo. Nesse endpoint, a anotação @RolesAllowed impõe a restrição de acesso de forma declarativa.

/api/users/me

O endpoint /api/users/me é protegido por RBAC. Somente os usuários que têm a função user podem acessar o endpoint. Esse endpoint retorna o nome de usuário do chamador como uma cadeia de caracteres.

Para examinar o exemplo concluído, faça o download do archive ou clone o repositório Git:

git clone -b 3.2 https://github.com/quarkusio/quarkus-quickstarts.git

Você pode encontrar a solução no diretório security-jpa-quickstart na comunidade upstream do {ProductName}.

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 security-jpa. Tanto o Hibernate Reativo quanto o Hibernate Reativo com Panache podem ser usados com a extensão security-jpa-reactive.

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:

      CLI
      quarkus create app org.acme:security-jpa-quickstart \
          --no-code
      cd security-jpa-quickstart

      Para criar um projeto Gradle, adicione a opção --gradle ou --gradle-kotlin-dsl.

      Para obter mais informações sobre como instalar e usar a CLI do Quarkus, consulte o guia Quarkus CLI.

      Maven
      mvn io.quarkus.platform:quarkus-maven-plugin:3.2.12.Final:create \
          -DprojectGroupId=org.acme \
          -DprojectArtifactId=security-jpa-quickstart \
          -DnoCode
      cd security-jpa-quickstart

      Para criar um projeto Gradle, adicione a opção '-DbuildTool=gradle' ou '-DbuildTool=gradle-kotlin-dsl'.

    • Para criar o projeto Maven com o Hibernate Reativo, use o seguinte comando:

      CLI
      quarkus create app org.acme:security-jpa-reactive-quickstart \
          --no-code
      cd security-jpa-reactive-quickstart

      Para criar um projeto Gradle, adicione a opção --gradle ou --gradle-kotlin-dsl.

      Para obter mais informações sobre como instalar e usar a CLI do Quarkus, consulte o guia Quarkus CLI.

      Maven
      mvn io.quarkus.platform:quarkus-maven-plugin:3.2.12.Final:create \
          -DprojectGroupId=org.acme \
          -DprojectArtifactId=security-jpa-reactive-quickstart \
          -DnoCode
      cd security-jpa-reactive-quickstart

      Para criar um projeto Gradle, adicione a opção '-DbuildTool=gradle' ou '-DbuildTool=gradle-kotlin-dsl'.

  • 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:

      CLI
      quarkus 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:

      CLI
      quarkus 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.gradle
    implementation("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.gradle
    implementation("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/public para 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/public para 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 @RolesAllowed para garantir que somente os usuários aos quais foi concedida a função admin possam 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/me que só possa ser acessado por usuários que tenham a função user. Use SecurityContext para obter acesso ao usuário Principal autenticado 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

  1. Ative o mecanismo de autenticação básico do Quarkus integrado definindo a propriedade quarkus.http.auth.basic como true :

    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.basic como true .

  2. Configure pelo menos uma fonte de dados no arquivo application.properties para que a extensão security-jpa possa 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
  3. 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:

  • Os URLs dos recursos de dados reativos usados pela extensão security-jpa-reactive são definidos com a propriedade de configuração quarkus.datasource.reactive.url e não com a propriedade de configuração quarkus.datasource.jdbc.url , que normalmente é usada pelos recursos de dados JDBC.

    %prod.quarkus.datasource.reactive.url=vertx-reactive:postgresql://localhost:5431/security_jpa
  • Neste tutorial, um banco de dados PostgreSQL é usado para o armazenamento de identidade. O Hibernate ORM cria automaticamente o esquema do banco de dados na inicialização. Essa abordagem é adequada para o desenvolvimento, mas não é recomendada para a produção. Portanto, são necessários ajustes em um ambiente de produção.

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 security-jpa tem como padrão o uso de senhas com hash bcrypt.

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:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./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, o Dev Services para PostgreSQL inicia e configura um contêiner de teste PostgreSQL .

%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 em Dev Services para PostgreSQL e 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, Dev Services for PostgreSQL launches a PostgreSQL devmode container so that you can start developing your application. While developing your application, you can add tests one by one and run them using the Continuous Testing feature. Dev Services for PostgreSQL supports testing while you develop by providing a separate PostgreSQL test container that does not conflict with the devmode container.

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

      1. Compile a aplicação:

        CLI
        quarkus build
        Maven
        ./mvnw install
        Gradle
        ./gradlew build
      2. Execute a aplicação:

        java -jar target/quarkus-app/quarkus-run.jar
    • Modo nativo

      1. Compile a aplicação:

        CLI
        quarkus build --native
        Maven
        ./mvnw install -Dnative
        Gradle
        ./gradlew build -Dquarkus.package.type=native
      2. 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: