Como projetar um sistema de permissões escalável
1. Fundamentos de um sistema de permissões escalável
Um sistema de permissões escalável precisa suportar milhões de usuários, bilhões de recursos e milhares de regras sem degradar a latência. A escalabilidade horizontal — adicionar mais servidores — é preferível à vertical (aumentar recursos de uma única máquina), pois permite crescimento elástico.
Os principais desafios incluem:
- Latência: decisões de autorização precisam ser tomadas em milissegundos para não impactar a experiência do usuário
- Consistência: em sistemas distribuídos, mudanças nas permissões devem se propagar rapidamente sem causar falhas de segurança
- Volume de regras: com centenas de milhares de políticas, a avaliação eficiente se torna crítica
É essencial diferenciar três conceitos:
- Autenticação: verifica "quem é você" (login, tokens JWT)
- Autorização: determina "o que você pode fazer" (permissões)
- Auditoria: registra "o que foi feito" (logs de decisões)
text
// Exemplo de estrutura de decisão de autorização
{
"subject": "user_123",
"action": "read",
"resource": "document_456",
"context": {
"ip": "192.168.1.1",
"time": "2024-01-15T10:30:00Z"
},
"decision": "allow"
}
2. Modelos de permissões: RBAC, ABAC e ReBAC
RBAC (Role-Based Access Control)
Usa papéis hierárquicos com herança de permissões. Simples e eficiente, mas pode se tornar rígido.
text
// Exemplo de RBAC com hierarquia
roles:
admin:
permissions: [read, write, delete, manage_users]
editor:
permissions: [read, write]
inherits: [viewer]
viewer:
permissions: [read]
user_roles:
alice: [admin]
bob: [editor]
charlie: [viewer]
ABAC (Attribute-Based Access Control)
Avalia permissões com base em atributos do usuário, recurso, ação e ambiente. Mais flexível, mas complexo de gerenciar.
text
// Política ABAC exemplo
policy:
rule: "allow_if"
conditions:
- attribute: "user.department"
operator: "equals"
value: "engineering"
- attribute: "resource.type"
operator: "equals"
value: "code_repository"
- attribute: "action"
operator: "in"
value: ["read", "write"]
ReBAC (Relationship-Based Access Control)
Baseado em grafos de relacionamento. Ideal para redes sociais, sistemas colaborativos e organizações hierárquicas.
text
// Grafo ReBAC
users:
- alice: { manager_of: [bob, charlie] }
- bob: { member_of: [team_alpha] }
- charlie: { member_of: [team_beta] }
resources:
- project_x: { owned_by: [alice], shared_with: [team_alpha] }
// Avaliação: alice pode ler project_x? Sim (owner)
// bob pode ler project_x? Sim (membro do team_alpha)
// charlie pode ler project_x? Não (não tem relacionamento)
3. Arquitetura de dados para permissões em larga escala
A escolha do armazenamento impacta diretamente a escalabilidade:
- Bancos relacionais (PostgreSQL): bom para RBAC com joins simples, mas limitado para consultas complexas
- NoSQL (DynamoDB, Cassandra): melhor para alta disponibilidade e consultas por chave, porém sem joins nativos
- Bancos de grafos (Neo4j, Dgraph): ideais para ReBAC, com consultas eficientes em relacionamentos profundos
Estratégias de cache distribuído com Redis ou Memcached reduzem a latência:
text
// Estrutura de cache de permissões
cache_key: "permissions:user_123:resource_456"
cache_value: {
"can_read": true,
"can_write": false,
"can_delete": false,
"expires_at": 1705314000
}
// Tempo médio de resposta com cache: 2ms
// Tempo médio de resposta sem cache: 50ms
Para multi-tenancy, o sharding por tenant isola regras e melhora a performance:
text
// Sharding por tenant_id
shard_1: tenants [A-M]
shard_2: tenants [N-Z]
// Roteamento baseado em hash do tenant_id
function getShard(tenantId) {
return hash(tenantId) % numberOfShards;
}
4. Motor de avaliação de permissões (Policy Engine)
Abordagens de implantação
- Centralizada: um serviço dedicado avalia todas as permissões (ex: OPA, Casbin Server)
- Embarcada (sidecar): o motor roda junto com a aplicação (ex: Casbin library, Cedar)
Implementação de algoritmos de decisão
Árvores de decisão e avaliação lazy permitem processar apenas as regras necessárias:
text
// Algoritmo de avaliação lazy
function evaluatePolicy(user, action, resource, policies):
applicablePolicies = []
for policy in policies:
if matchesSubject(policy, user)
and matchesAction(policy, action)
and matchesResource(policy, resource):
applicablePolicies.append(policy)
// Early exit: primeira regra "deny" bloqueia
for policy in applicablePolicies:
if policy.effect == "deny":
return "deny"
// Se nenhuma regra "allow" explícita, negar por padrão
for policy in applicablePolicies:
if policy.effect == "allow":
return "allow"
return "deny" // default deny
Linguagens de política populares
- Open Policy Agent (OPA): usa Rego, poderoso para ABAC complexo
- Casbin: suporta RBAC, ABAC e múltiplos backends de armazenamento
- Cedar (AWS): focado em desempenho e segurança, usado no AWS Verified Permissions
text
// Exemplo de política em Rego (OPA)
package authz
default allow = false
allow {
input.action == "read"
input.resource.type == "document"
data.roles[input.subject.role].permissions[_] == "read"
}
allow {
input.action == "write"
input.resource.owner == input.subject.id
}
5. Sincronização e consistência em sistemas distribuídos
Em sistemas distribuídos, mudanças de permissões precisam ser propagadas rapidamente. A consistência eventual é aceitável para muitos cenários, mas algumas operações críticas (como revogação de acesso) exigem consistência forte.
Uso de event sourcing para rastrear alterações:
text
// Eventos de mudança de permissão
event_1: {
"type": "role_assigned",
"user": "user_123",
"role": "admin",
"timestamp": 1705314000
}
event_2: {
"type": "permission_revoked",
"user": "user_456",
"resource": "document_789",
"action": "delete",
"timestamp": 1705314100
}
Estratégias de invalidação de cache:
- TTL (Time-to-Live): expiração automática após tempo definido
- Invalidation por evento: quando uma permissão muda, publica-se um evento para limpar cache
- Write-through: atualiza cache e banco simultaneamente
text
// Invalidação de cache baseada em eventos
function onPermissionChange(userId, resourceId) {
cacheKey = `permissions:${userId}:${resourceId}`;
redis.del(cacheKey);
// Publicar evento para outros serviços
messageQueue.publish("permission.changed", {
userId, resourceId, timestamp: Date.now()
});
}
6. Otimizações de desempenho e redução de latência
Pré-computação de permissões
Para consultas frequentes, materialize as permissões em tabelas ou caches:
text
// Tabela materializada de permissões
user_id | resource_id | can_read | can_write | can_delete
user_123| doc_456 | true | true | false
user_456| doc_456 | true | false | false
Indexação eficiente
Índices compostos por usuário, recurso e ação aceleram consultas:
text
-- Índices para consultas de permissão
CREATE INDEX idx_permissions_user_resource
ON permissions(user_id, resource_id);
CREATE INDEX idx_permissions_action
ON permissions(action);
Batching e paralelismo
Para avaliar múltiplas permissões simultaneamente, use processamento em lote:
text
// Batching de consultas de permissão
function batchCheckPermissions(userId, resourceIds, action) {
// Consulta única com múltiplos recursos
const results = db.query(`
SELECT resource_id,
EXISTS(SELECT 1 FROM permissions
WHERE user_id = $1
AND resource_id = ANY($2)
AND action = $3) as allowed
FROM unnest($2) as resource_id
`, [userId, resourceIds, action]);
return results;
}
7. Monitoramento, auditoria e evolução do sistema
Logs de decisão
Cada decisão de autorização deve ser registrada para compliance e debugging:
text
// Log de decisão de autorização
{
"decision_id": "dec_789012",
"timestamp": "2024-01-15T10:30:00.123Z",
"subject": "user_123",
"action": "delete",
"resource": "document_456",
"decision": "deny",
"reason": "user_not_owner",
"policies_evaluated": 5,
"evaluation_time_ms": 3.2
}
Métricas de performance
Monitore continuamente:
- Tempo médio de avaliação (alvo: <10ms)
- Taxa de acerto de cache (alvo: >95%)
- Número de regras avaliadas por decisão
- Latência P99 das consultas
Versionamento de políticas
Permite rollback seguro em caso de erros:
text
// Versionamento de política
policy_version_1:
created_at: "2024-01-01T00:00:00Z"
rules: [/* regras antigas */]
policy_version_2:
created_at: "2024-01-15T00:00:00Z"
rules: [/* regras novas */]
// Rollback para versão anterior
function rollbackPolicy(policyId, targetVersion) {
const policy = getPolicyVersion(policyId, targetVersion);
activatePolicy(policyId, policy);
logRollback(policyId, targetVersion);
}
Projetar um sistema de permissões escalável exige equilibrar flexibilidade, desempenho e segurança. Comece com RBAC para simplicidade, evolua para ABAC ou ReBAC conforme a complexidade aumentar. Invista em cache, indexação e monitoramento desde o início para garantir que o sistema cresça sem comprometer a experiência do usuário.
Referências
- Open Policy Agent Documentation — Documentação oficial do OPA, com guias sobre políticas Rego, integração e deploy em larga escala
- Casbin: An Open-Source Authorization Library — Tutorial completo sobre modelos RBAC, ABAC e suporte a múltiplos backends de armazenamento
- AWS Cedar Policy Language — Documentação oficial da linguagem Cedar, usada no AWS Verified Permissions, com exemplos de políticas e avaliação
- Google Zanzibar: Google's Consistent, Global Authorization System — Paper acadêmico sobre o sistema de permissões global do Google, base para muitos sistemas ReBAC modernos
- Redis Cache Patterns for Authorization — Guia oficial do Redis sobre padrões de cache, incluindo invalidação e estratégias para sistemas de permissões distribuídos
- Neo4j Graph Database for Relationship-Based Access Control — Artigo técnico sobre implementação de ReBAC usando bancos de grafos, com exemplos de consultas Cypher
- Authorization in Microservices: Patterns and Practices — Catálogo de padrões de autorização para microsserviços, incluindo API Gateway, sidecar e policy engine centralizado