Proteja sua API Vert.x Web com RBAC (controle de acesso baseado em papel) e validação de JWT
Este guia irá ajudá-lo a implementar autorização para proteger suas APIs Vert.x Web 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 Vert.x Web.

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 Vert.x Web 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:
- Recursos globais de API
- Permissões de organização (não-API)
- Recursos de API em nível de organização

- 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

- Caso de uso: Controlar ações específicas da organização, recursos de UI ou lógica de negócios (não APIs)
- Tipo de token: Token de organização (Organization token) com público específico da organização
- Exemplos: Liberação de recursos, permissões de dashboard, controles de convite de membros
- Melhor para: SaaS multi-inquilino com recursos e fluxos de trabalho específicos de organização
- Saiba mais: Proteger permissões de organização (não-API)

- Caso de uso: Proteger recursos de API acessíveis dentro de um contexto específico de organização
- Tipo de token: Token de organização (Organization token) com público de recurso de API + contexto de organização
- Exemplos: APIs multi-inquilino, endpoints de dados com escopo de organização, microsserviços específicos de locatário
- Melhor para: SaaS multi-inquilino onde os dados da API têm escopo de organização
- Saiba mais: Proteger recursos de API em nível de organização
💡 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
- Recursos globais de API
- Permissões de organização (não-API)
- Recursos de API em nível de organização
- Criar recurso de API: Vá para Console → Recursos de API e registre sua API (ex:
https://api.seuapp.com
) - Definir permissões: Adicione escopos como
read:products
,write:orders
– veja Definir recursos de API com permissões - 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
- Atribuir papéis: Atribua papéis a usuários ou aplicativos M2M que precisam de acesso à API
- Definir permissões da organização: Crie permissões de organização não relacionadas à API como
invite:member
,manage:billing
no template da organização - Configurar papéis da organização: Configure o template da organização com papéis específicos da organização e atribua permissões a eles
- Atribuir papéis da organização: Atribua usuários aos papéis da organização dentro do contexto de cada organização
- Criar recurso de API: Registre seu recurso de API como acima, mas ele será usado no contexto da organização
- Definir permissões: Adicione escopos como
read:data
,write:settings
que são restritos ao contexto da organização - Configurar template da organização: Configure papéis da organização que incluam as permissões do seu recurso de API
- Atribuir papéis da organização: Atribua usuários ou aplicativos M2M a papéis da organização que incluam permissões de API
- Configuração multi-tenant: Certifique-se de que sua API pode lidar com dados e validação com escopo de organização
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:
- Autenticação de usuário: Atualize seu app → para solicitar os escopos da sua API e/ou contexto de organização
- Máquina para máquina: Configure escopos M2M → para acesso servidor a servidor
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
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 Vert.x Web, você pode criar um projeto Maven manualmente:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>your-api-name</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<vertx.version>4.5.0</vertx.version>
</properties>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-jwt</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>${vertx.version}</version>
</dependency>
</dependencies>
</project>
Crie um servidor básico Vert.x Web:
package com.example;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
public class MainVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) throws Exception {
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
router.get("/hello").handler(ctx -> {
ctx.response()
.putHeader("content-type", "text/plain")
.end("Hello from Vert.x Web!");
});
vertx.createHttpServer()
.requestHandler(router)
.listen(3000, http -> {
if (http.succeeded()) {
startPromise.complete();
System.out.println("Servidor HTTP iniciado na porta 3000");
} else {
startPromise.fail(http.cause());
}
});
}
}
package com.example;
import io.vertx.core.Vertx;
public class Application {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
vertx.deployVerticle(new MainVerticle());
}
}
Consulte a documentação do Vert.x Web para mais detalhes sobre como configurar rotas, handlers e outros recursos.
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)>
.
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"
}
Definir manualmente no seu código (não recomendado)
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:
- Recursos de API globais
- Permissões de organização (não-API)
- Recursos de API em nível de organizaçã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
- Reivindicação de público (
aud
):urn:logto:organization:<id>
(contexto da organização está emaud
) - Reivindicação de organização (
organization_id
): Não presente - Escopos (permissões) a verificar (
scope
): Permissões da organização
- Reivindicação de público (
aud
): Indicador de recurso de API - Reivindicação de organização (
organization_id
): ID da organização (deve corresponder à requisição) - 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.
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.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-jwt</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
</dependency>
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.auth.jwt.JWTAuthOptions;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.client.WebClient;
import java.util.List;
import java.util.ArrayList;
public class JwtAuthHandler implements Handler<RoutingContext> {
private final JWTAuth jwtAuth;
private final WebClient webClient;
private final String expectedIssuer;
private final String jwksUri;
public JwtAuthHandler(Vertx vertx) {
this.webClient = WebClient.create(vertx);
this.jwtAuth = JWTAuth.create(vertx, new JWTAuthOptions());
// Lembre-se de definir essas variáveis de ambiente em seu deployment
this.expectedIssuer = System.getenv("JWT_ISSUER");
this.jwksUri = System.getenv("JWKS_URI");
// Buscar JWKS e configurar autenticação JWT
fetchJWKS().onSuccess(jwks -> {
// Configurar JWKS (simplificado - talvez você precise de um parser JWKS adequado)
});
}
@Override
public void handle(RoutingContext context) {
String authHeader = context.request().getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
context.response()
.setStatusCode(401)
.putHeader("Content-Type", "application/json")
.end("{\"error\": \"Authorization header missing or invalid\"}");
return;
}
String token = authHeader.substring(7);
jwtAuth.authenticate(new JsonObject().put("jwt", token))
.onSuccess(user -> {
try {
JsonObject principal = user.principal();
verifyPayload(principal);
context.put("auth", principal);
context.next();
} catch (AuthorizationException e) {
context.response()
.setStatusCode(e.getStatusCode()) // Usar o status code da exceção
.putHeader("Content-Type", "application/json")
.end("{\"error\": \"" + e.getMessage() + "\"}");
} catch (Exception e) {
context.response()
.setStatusCode(401)
.putHeader("Content-Type", "application/json")
.end("{\"error\": \"Invalid token\"}");
}
})
.onFailure(err -> {
context.response()
.setStatusCode(401)
.putHeader("Content-Type", "application/json")
.end("{\"error\": \"Invalid token: " + err.getMessage() + "\"}");
});
}
private Future<JsonObject> fetchJWKS() {
return webClient.getAbs(this.jwksUri)
.send()
.map(response -> response.bodyAsJsonObject());
}
private void verifyPayload(JsonObject principal) {
// Verificar emissor manualmente para Vert.x
String issuer = principal.getString("iss");
if (issuer == null || !expectedIssuer.equals(issuer)) {
throw new AuthorizationException("Invalid issuer: " + issuer);
}
// Implemente aqui sua lógica adicional de verificação baseada no modelo de permissão
// Use os métodos auxiliares abaixo para extração de reivindicações
}
// Métodos auxiliares para JWT no Vert.x
private List<String> extractAudiences(JsonObject principal) {
JsonArray audiences = principal.getJsonArray("aud");
if (audiences != null) {
List<String> result = new ArrayList<>();
for (Object aud : audiences) {
result.add(aud.toString());
}
return result;
}
return List.of();
}
private String extractScopes(JsonObject principal) {
return principal.getString("scope");
}
private String extractOrganizationId(JsonObject principal) {
return principal.getString("organization_id");
}
}
De acordo com seu modelo de permissão, implemente a lógica de verificação apropriada:
- Recursos globais de API
- Permissões de organização (não-API)
- Recursos de API em nível de organização
// 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");
}
// Verifique se a reivindicação de público corresponde ao formato da organização
List<String> audiences = extractAudiences(token); // Extração específica do framework
boolean hasOrgAudience = audiences.stream()
.anyMatch(aud -> aud.startsWith("urn:logto:organization:"));
if (!hasOrgAudience) {
throw new AuthorizationException("Público inválido para permissões de organização");
}
// Verifique se o ID da organização corresponde ao contexto (você pode precisar extrair isso do contexto da requisição)
String expectedOrgId = "your-organization-id"; // Extraia do contexto da requisição
String expectedAud = "urn:logto:organization:" + expectedOrgId;
if (!audiences.contains(expectedAud)) {
throw new AuthorizationException("ID da organização não corresponde");
}
// Verifique os escopos necessários da organização
List<String> requiredScopes = Arrays.asList("invite:users", "manage:settings"); // 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 de organização insuficiente");
}
// 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 para recursos de API em nível de organização");
}
// Verifique se o ID da organização corresponde ao contexto (você pode precisar extrair isso do contexto da requisição)
String expectedOrgId = "your-organization-id"; // Extraia do contexto da requisição
String orgId = extractOrganizationId(token); // Extração específica do framework
if (!expectedOrgId.equals(orgId)) {
throw new AuthorizationException("ID da organização não corresponde");
}
// Verifique os escopos necessários para recursos de API em nível de organização
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("Escopos de API em nível de organização insuficientes");
}
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.
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
public class MainVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) throws Exception {
Router router = Router.router(vertx);
// Aplicar middleware às rotas protegidas
router.route("/api/protected*").handler(new JwtAuthHandler(vertx));
router.get("/api/protected").handler(this::protectedEndpoint);
vertx.createHttpServer()
.requestHandler(router)
.listen(8080, result -> {
if (result.succeeded()) {
startPromise.complete();
} else {
startPromise.fail(result.cause());
}
});
}
private void protectedEndpoint(RoutingContext context) {
// Acessar o principal do JWT diretamente do contexto
JsonObject principal = context.get("auth");
if (principal == null) {
context.response()
.setStatusCode(500)
.putHeader("Content-Type", "application/json")
.end("{\"error\": \"Principal do JWT não encontrado\"}");
return;
}
String scopes = principal.getString("scope");
JsonObject response = new JsonObject()
.put("sub", principal.getString("sub"))
.put("client_id", principal.getString("client_id"))
.put("organization_id", principal.getString("organization_id"))
.put("scopes", scopes != null ? scopes.split(" ") : new String[0])
.put("audience", principal.getJsonArray("aud"));
context.response()
.putHeader("Content-Type", "application/json")
.end(response.encode());
}
}
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:
-
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.
-
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
escope
de acordo com seu recurso de API e permissões; um parâmetroorganization_id
também pode ser exigido se sua API for voltada para organização.
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
- Recursos de API globais
- Permissões de organização (não-API)
- Recursos de API em nível de organizaçã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
Cenários de teste para controle de acesso específico de organização:
- Token de organização válido: Teste com tokens que incluam o contexto correto da organização (ID da organização e escopos)
- Escopos ausentes: Espere 403 Proibido quando o usuário não tiver permissões para a ação solicitada
- Organização errada: Espere 403 Proibido quando o público não corresponder ao contexto da organização (
urn:logto:organization:<organization_id>
)
# Token para organização errada - espera-se 403
curl -H "Authorization: Bearer token-for-different-organization" \
http://localhost:3000/api/protected
Cenários de teste combinando validação de recurso de API com contexto de organização:
- Organização válida + escopos de API: Teste com tokens que tenham tanto o contexto da organização quanto os escopos de API necessários
- Escopos de API ausentes: Espere 403 Proibido quando o token de organização não tiver as permissões de API necessárias
- Organização errada: Espere 403 Proibido ao acessar a API com token de outra organização
- Público errado: Espere 403 Proibido quando o público não corresponder ao recurso de API em nível de organização
# Token de organização sem escopos de API - espera-se 403
curl -H "Authorization: Bearer organization-token-without-api-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