Pular para o conteúdo principal

Proteja sua API Quarkus com RBAC e validação de JWT

Este guia irá ajudá-lo a implementar autorização para proteger suas APIs Quarkus usando controle de acesso baseado em papel (RBAC) e JSON Web Tokens (JWTs) emitidos pelo Logto.

Antes de começar​

Seus aplicativos cliente precisam obter tokens de acesso (Access tokens) do Logto. Se você ainda não configurou a integração do cliente, confira nossos Guias rápidos para React, Vue, Angular ou outros frameworks de cliente, ou veja nosso Guia máquina para máquina para acesso servidor a servidor.

Este guia foca na validação no lado do servidor desses tokens em seu aplicativo Quarkus.

Uma figura mostrando o foco deste guia

O que você vai aprender​

  • Validação de JWT: Aprenda a validar tokens de acesso (Access tokens) e extrair informações de autenticação (Authentication)
  • Implementação de middleware: Crie middleware reutilizável para proteção de API
  • Modelos de permissão: Entenda e implemente diferentes padrões de autorização (Authorization):
    • Recursos de API globais para endpoints de aplicação
    • Permissões de organização para controle de funcionalidades específicas do locatário
    • Recursos de API em nível de organização para acesso a dados multi-inquilino
  • Integração com RBAC: Implemente permissões e escopos baseados em papel (Role-based access control (RBAC)) em seus endpoints de API

Pré-requisitos​

  • Última versão estável do Java instalada
  • Compreensão básica de Quarkus e desenvolvimento de API web
  • Um aplicativo Logto configurado (veja Guias rápidos se necessário)

Visão geral dos modelos de permissão​

Antes de implementar a proteção, escolha o modelo de permissão que se encaixa na arquitetura do seu aplicativo. Isso está alinhado com os três principais cenários de autorização do Logto:

RBAC de recursos globais de API
  • Caso de uso: Proteger recursos de API compartilhados em todo o seu aplicativo (não específicos de organização)
  • Tipo de token: Token de acesso (Access token) com público global (global audience)
  • Exemplos: APIs públicas, serviços principais do produto, endpoints de administração
  • Melhor para: Produtos SaaS com APIs usadas por todos os clientes, microsserviços sem isolamento de locatário
  • Saiba mais: Proteger recursos globais de API

💡 Escolha seu modelo antes de prosseguir – a implementação fará referência à abordagem escolhida ao longo deste guia.

Passos rápidos de preparação​

Configure recursos e permissões do Logto​

  1. Criar recurso de API: Vá para Console → Recursos de API e registre sua API (ex: https://api.seuapp.com)
  2. Definir permissões: Adicione escopos como read:products, write:orders – veja Definir recursos de API com permissões
  3. Criar papéis globais: Vá para Console → Papéis e crie papéis que incluam as permissões da sua API – veja Configurar papéis globais
  4. Atribuir papéis: Atribua papéis a usuários ou aplicativos M2M que precisam de acesso à API
Novo no RBAC?:

Comece com nosso guia de controle de acesso baseado em papel para instruções passo a passo de configuração.

Atualize seu aplicativo cliente​

Solicite os escopos apropriados em seu cliente:

O processo geralmente envolve atualizar a configuração do seu cliente para incluir um ou mais dos seguintes:

  • Parâmetro scope nos fluxos OAuth
  • Parâmetro resource para acesso a recursos de API
  • organization_id para contexto de organização
Antes de codificar:

Certifique-se de que o usuário ou app M2M que você está testando recebeu os papéis ou papéis de organização adequados que incluam as permissões necessárias para sua API.

Inicialize seu projeto de API​

Para inicializar um novo projeto Quarkus, você pode usar o Quarkus CLI ou o Maven:

Usando o Quarkus CLI:

quarkus create app com.example:your-api-name \
--extension='resteasy-reactive,smallrye-jwt'
cd your-api-name

Ou usando o Maven:

mvn io.quarkus.platform:quarkus-maven-plugin:3.6.0:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=your-api-name \
-Dextensions="resteasy-reactive,smallrye-jwt"
cd your-api-name

Isso criará um projeto Quarkus básico com as dependências necessárias:

pom.xml
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>your-api-name</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<compiler-plugin.version>3.11.0</compiler-plugin.version>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.6.0</quarkus.platform.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

Crie um recurso básico:

src/main/java/com/example/ExampleResource.java
package com.example;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/hello")
public class ExampleResource {

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from Quarkus REST";
}
}
nota:

Consulte a documentação do Quarkus para mais detalhes sobre como configurar recursos, serviços e outras funcionalidades.

Inicialize constantes e utilitários​

Defina as constantes e utilitários necessários em seu código para lidar com a extração e validação do token. Uma solicitação válida deve incluir um cabeçalho Authorization no formato Bearer <token de acesso (access token)>.

AuthorizationException.java
public class AuthorizationException extends RuntimeException {
private final int statusCode;

public AuthorizationException(String message) {
this(message, 403); // Padrão para 403 Proibido (Forbidden)
}

public AuthorizationException(String message, int statusCode) {
super(message);
this.statusCode = statusCode;
}

public int getStatusCode() {
return statusCode;
}
}

Recupere informações sobre seu tenant Logto​

Você precisará dos seguintes valores para validar tokens emitidos pelo Logto:

  • URI do JSON Web Key Set (JWKS): A URL para as chaves públicas do Logto, usada para verificar assinaturas de JWT.
  • Emissor (Issuer): O valor esperado do emissor (URL OIDC do Logto).

Primeiro, encontre o endpoint do seu tenant Logto. Você pode encontrá-lo em vários lugares:

  • No Logto Console, em Configurações → Domínios.
  • Em qualquer configuração de aplicativo onde você configurou no Logto, Configurações → Endpoints & Credenciais.

Buscar no endpoint de descoberta do OpenID Connect​

Esses valores podem ser obtidos no endpoint de descoberta do OpenID Connect do Logto:

https://<seu-endpoint-logto>/oidc/.well-known/openid-configuration

Aqui está um exemplo de resposta (outros campos omitidos para brevidade):

{
"jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
"issuer": "https://your-tenant.logto.app/oidc"
}

Como o Logto não permite personalizar o URI do JWKS ou o emissor, você pode definir esses valores manualmente no seu código. No entanto, isso não é recomendado para aplicações em produção, pois pode aumentar a sobrecarga de manutenção caso alguma configuração mude no futuro.

  • URI do JWKS: https://<seu-endpoint-logto>/oidc/jwks
  • Emissor: https://<seu-endpoint-logto>/oidc

Valide o token e as permissões​

Após extrair o token e buscar a configuração OIDC, valide o seguinte:

  • Assinatura: O JWT deve ser válido e assinado pelo Logto (via JWKS).
  • Emissor (Issuer): Deve corresponder ao emissor do seu tenant Logto.
  • Público (Audience): Deve corresponder ao indicador de recurso da API registrado no Logto, ou ao contexto da organização se aplicável.
  • Expiração: O token não pode estar expirado.
  • Permissões (escopos) (Permissions (scopes)): O token deve incluir os escopos necessários para sua API / ação. Os escopos são strings separadas por espaço na reivindicação scope.
  • Contexto da organização: Se estiver protegendo recursos de API em nível de organização, valide a reivindicação organization_id.

Veja JSON Web Token para saber mais sobre a estrutura e reivindicações do JWT.

O que verificar para cada modelo de permissão​

As reivindicações e regras de validação diferem conforme o modelo de permissão:

  • Reivindicação de público (aud): Indicador de recurso de API
  • Reivindicação de organização (organization_id): Não presente
  • Escopos (permissões) a verificar (scope): Permissões do recurso de API

Para permissões de organização que não são de API, o contexto da organização é representado pela reivindicação aud (por exemplo, urn:logto:organization:abc123). A reivindicação organization_id só está presente para tokens de recursos de API em nível de organização.

dica:

Sempre valide tanto as permissões (escopos) quanto o contexto (público, organização) para APIs multi-tenant seguras.

Adicione a lógica de validação​

Utilizamos diferentes bibliotecas JWT dependendo do framework. Instale as dependências necessárias:

Adicione ao seu pom.xml:

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
application.properties
# Configuração do JWT
mp.jwt.verify.publickey.location=${JWKS_URI:https://your-tenant.logto.app/oidc/jwks}
mp.jwt.verify.issuer=${JWT_ISSUER:https://your-tenant.logto.app/oidc}
JwtVerificationFilter.java
import org.eclipse.microprofile.jwt.JsonWebToken;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;
import java.util.ArrayList;
import java.util.List;

@Provider
@ApplicationScoped
public class JwtVerificationFilter implements ContainerRequestFilter {

@Inject
JsonWebToken jwt;

@Override
public void filter(ContainerRequestContext requestContext) {
if (requestContext.getUriInfo().getPath().startsWith("/api/protected")) {
try {
verifyPayload(jwt);
requestContext.setProperty("auth", jwt);
} catch (AuthorizationException e) {
requestContext.abortWith(
Response.status(e.getStatusCode())
.entity("{\"error\": \"" + e.getMessage() + "\"}")
.build()
);
} catch (Exception e) {
requestContext.abortWith(
Response.status(401)
.entity("{\"error\": \"Token inválido\"}")
.build()
);
}
}
}

private void verifyPayload(JsonWebToken jwt) {
// A validação do emissor (Issuer) é feita automaticamente pela extensão Quarkus JWT
// Implemente aqui sua lógica adicional de verificação com base no modelo de permissão
// Use os métodos auxiliares abaixo para extração de reivindicações (claims)
}

// Métodos auxiliares para Quarkus JWT
private List<String> extractAudiences(JsonWebToken jwt) {
return new ArrayList<>(jwt.getAudience());
}

private String extractScopes(JsonWebToken jwt) {
return jwt.getClaim("scope");
}

private String extractOrganizationId(JsonWebToken jwt) {
return jwt.getClaim("organization_id");
}
}

De acordo com seu modelo de permissão, implemente a lógica de verificação apropriada:

// Verifique se a reivindicação de público corresponde ao seu indicador de recurso de API
List<String> audiences = extractAudiences(token); // Extração específica do framework
if (!audiences.contains("https://your-api-resource-indicator")) {
throw new AuthorizationException("Público inválido");
}

// Verifique os escopos necessários para recursos globais de API
List<String> requiredScopes = Arrays.asList("api:read", "api:write"); // Substitua pelos seus escopos necessários
String scopes = extractScopes(token); // Extração específica do framework
List<String> tokenScopes = scopes != null ? Arrays.asList(scopes.split(" ")) : List.of();

if (!tokenScopes.containsAll(requiredScopes)) {
throw new AuthorizationException("Escopo insuficiente");
}

Os métodos auxiliares para extração de reivindicações são específicos de cada framework. Veja os detalhes da implementação nos arquivos de validação específicos do framework acima.

Aplique o middleware à sua API​

Agora, aplique o middleware às suas rotas de API protegidas.

ProtectedResource.java
import org.eclipse.microprofile.jwt.JsonWebToken;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.container.ContainerRequestContext;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

@Path("/api")
public class ProtectedResource {

@Inject
JsonWebToken jwt;

@GET
@Path("/protected")
@Produces(MediaType.APPLICATION_JSON)
public Map<String, Object> protectedEndpoint(@Context ContainerRequestContext requestContext) {
// Acessa o JWT diretamente da injeção ou do contexto
JsonWebToken token = (JsonWebToken) requestContext.getProperty("auth");
if (token == null) {
token = jwt; // Alternativa para o JWT injetado
}

String scopes = token.getClaim("scope");
List<String> scopeList = scopes != null ? Arrays.asList(scopes.split(" ")) : List.of();

return Map.of(
"sub", token.getSubject(),
"client_id", token.<String>getClaim("client_id"),
"organization_id", token.<String>getClaim("organization_id"),
"scopes", scopeList,
"audience", token.getAudience()
);
}
}

Teste sua API protegida​

Obter tokens de acesso (Access tokens)​

Do seu aplicativo cliente: Se você configurou uma integração de cliente, seu aplicativo pode obter tokens automaticamente. Extraia o token de acesso e use-o nas requisições de API.

Para testes com curl / Postman:

  1. Tokens de usuário: Use as ferramentas de desenvolvedor do seu aplicativo cliente para copiar o token de acesso do localStorage ou da aba de rede.

  2. Tokens máquina para máquina: Use o fluxo de credenciais do cliente. Aqui está um exemplo não normativo usando curl:

    curl -X POST https://your-tenant.logto.app/oidc/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials" \
    -d "client_id=your-m2m-client-id" \
    -d "client_secret=your-m2m-client-secret" \
    -d "resource=https://your-api-resource-indicator" \
    -d "scope=api:read api:write"

    Pode ser necessário ajustar os parâmetros resource e scope de acordo com seu recurso de API e permissões; um parâmetro organization_id também pode ser exigido se sua API for voltada para organização.

dica:

Precisa inspecionar o conteúdo do token? Use nosso decodificador de JWT para decodificar e verificar seus JWTs.

Testar endpoints protegidos​

Requisição com token válido
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:3000/api/protected

Resposta esperada:

{
"auth": {
"sub": "user123",
"clientId": "app456",
"organizationId": "org789",
"scopes": ["api:read", "api:write"],
"audience": ["https://your-api-resource-indicator"]
}
}
Token ausente
curl http://localhost:3000/api/protected

Resposta esperada (401):

{
"error": "Authorization header is missing"
}
Token inválido
curl -H "Authorization: Bearer invalid-token" \
http://localhost:3000/api/protected

Resposta esperada (401):

{
"error": "Invalid token"
}

Testes específicos do modelo de permissão​

Cenários de teste para APIs protegidas com escopos globais:

  • Escopos válidos: Teste com tokens que incluam os escopos de API necessários (por exemplo, api:read, api:write)
  • Escopos ausentes: Espere 403 Proibido quando o token não tiver os escopos necessários
  • Público errado: Espere 403 Proibido quando o público não corresponder ao recurso de API
# Token sem escopos necessários - espera-se 403
curl -H "Authorization: Bearer token-without-required-scopes" \
http://localhost:3000/api/protected

Leitura adicional​

RBAC na prática: Implementando autorização segura para seu aplicativo

Construa um aplicativo SaaS multi-inquilino: Um guia completo do design à implementação