Segurança em camadas
Como aplicar segurança em cada camada da arquitetura: frontend, BFF, microsserviços e banco de dados, com estratégias específicas para cada nível.
O princípio da defesa em profundidade
A segurança em uma arquitetura distribuída não pode depender de um único ponto de controle. Se um atacante superar uma barreira, deve encontrar outra. Esse princípio se chama defesa em profundidade: cada camada implementa suas próprias medidas de segurança, independentemente do que fazem as demais.
[Frontend] → [API Gateway] → [BFF] → [Microsserviços] → [Banco de dados]
Camada 1 Camada 2 Camada 3 Camada 4 Camada 5
Cada camada tem ameaças específicas e, portanto, precisa de proteções específicas.
Segurança no Frontend
O frontend é a superfície de ataque mais exposta: qualquer usuário pode inspecionar o código, manipular requests e modificar o DOM.
O que proteger
- Inputs do usuário: todo dado que o usuário insere é potencialmente malicioso
- Tokens de sessão: se um atacante obtiver o token, pode se passar pelo usuário
- Dados sensíveis: nunca armazenar segredos, chaves de API ou dados confidenciais no frontend
Estratégias-chave
Sanitização de inputs
Nunca confie nos dados do usuário. Valide e sanitize no frontend como primeira linha de defesa, mas sempre valide também no backend.
// ❌ Perigoso: inserir HTML do usuário diretamente
element.innerHTML = userInput;
// ✅ Seguro: usar textContent ou sanitizar
element.textContent = userInput;
// Ou usar uma biblioteca de sanitização como DOMPurify
element.innerHTML = DOMPurify.sanitize(userInput);
Armazenamento seguro de tokens
// ❌ Vulnerável a XSS: localStorage é acessível a partir de qualquer script
localStorage.setItem('token', jwt);
// ✅ Melhor: cookies httpOnly (configuradas pelo servidor)
// O frontend não consegue acessar o cookie, mas ele é enviado automaticamente
Set-Cookie: token=jwt; HttpOnly; Secure; SameSite=Strict
Content Security Policy (CSP)
Configure headers de CSP para limitar quais recursos a página pode carregar:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.ejemplo.com;
Segurança no API Gateway
O API Gateway é o ponto de entrada único do sistema. É o lugar ideal para implementar controles de segurança transversais.
Responsabilidades de segurança
- Terminação TLS: todas as conexões externas devem ser HTTPS
- Rate limiting: limitar o número de requests por IP ou por usuário
- Validação de tokens: verificar se o JWT é válido antes de rotear
- Filtragem de headers: remover headers internos que não devem chegar ao exterior
- Logging de acesso: registrar cada request para auditoria
Rate limiting
# Exemplo de configuração de rate limiting
rate_limit:
global:
requests_per_second: 100
per_ip:
requests_per_minute: 60
per_user:
requests_per_minute: 120
endpoints:
/auth/login:
requests_per_minute: 5 # Mais restritivo para login
Validação de tokens no Gateway
Request recebido
→ Tem header Authorization?
→ Não → É rota pública? → Sim → Permitir
→ Não → 401 Unauthorized
→ Sim → Token válido (assinatura, expiração)?
→ Não → 401 Unauthorized
→ Sim → Extrair claims, anexar ao request, rotear
Segurança no BFF
O BFF (Backend for Frontend) atua como intermediário entre o frontend e os microsserviços. Tem responsabilidades de segurança específicas.
Responsabilidades
- Transformação segura de dados: nunca expor dados internos ao frontend
- Validação de inputs: segunda linha de validação após o frontend
- Orquestração segura: ao combinar dados de múltiplos serviços, respeitar as permissões do usuário
- Tratamento de erros: não vazar informações internas nas mensagens de erro
Exemplo: filtragem de dados sensíveis
// Resposta do microsserviço (dados internos)
const userFromService = {
id: "user-123",
email: "maria@ejemplo.com",
passwordHash: "$2b$10$...", // Nunca expor!
internalId: "db-row-456", // Dado interno!
role: "admin",
lastLogin: "2024-01-15T10:30:00Z"
};
// Resposta do BFF ao frontend (dados filtrados)
const userForFrontend = {
id: userFromService.id,
email: userFromService.email,
role: userFromService.role,
lastLogin: userFromService.lastLogin
};
Tratamento seguro de erros
// ❌ Expõe informação interna
res.status(500).json({
error: "Connection refused to postgres://db-host:5432/users"
});
// ✅ Erro genérico para o frontend
res.status(500).json({
error: "Erro interno do servidor. Tente novamente."
});
// O detalhe é registrado nos logs internos
Segurança nos Microsserviços
Cada microsserviço deve assumir que os requests que recebe podem ser maliciosos, mesmo que venham de outros serviços internos.
Zero Trust entre serviços
O modelo Zero Trust estabelece que nenhum serviço confia automaticamente em outro, mesmo dentro da mesma rede:
- Cada serviço verifica a identidade de quem chamou
- Cada serviço valida as permissões para a operação solicitada
- A comunicação entre serviços é criptografada (mTLS)
Validação de inputs em cada serviço
// Cada microsserviço valida seus próprios inputs
function createOrder(data) {
// Validar estrutura
if (!data.userId || !data.items || data.items.length === 0) {
throw new ValidationError("Datos de orden inválidos");
}
// Validar tipos e intervalos
if (typeof data.userId !== 'string' || data.userId.length > 50) {
throw new ValidationError("userId inválido");
}
for (const item of data.items) {
if (item.quantity < 1 || item.quantity > 1000) {
throw new ValidationError("Cantidad fuera de rango");
}
}
// Processar somente após validar
return orderRepository.create(data);
}
Princípio do menor privilégio
Cada microsserviço só deve ter acesso aos recursos de que precisa:
- O serviço de pedidos não precisa de acesso à tabela de usuários
- O serviço de notificações não precisa poder modificar pedidos
- As credenciais de banco de dados são exclusivas por serviço
Segurança no Banco de Dados
O banco de dados é a última linha de defesa. Se um atacante chegar até aqui, os dados devem estar protegidos.
Estratégias-chave
Criptografia em repouso
Os dados sensíveis devem estar criptografados em disco:
-- Exemplo: colunas criptografadas
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL, -- Hash, não criptografia reversível
ssn_encrypted BYTEA, -- Dado sensível criptografado com AES-256
created_at TIMESTAMP DEFAULT NOW()
);
Acesso com menor privilégio
-- Cada serviço tem seu próprio usuário de BD com permissões limitadas
-- Serviço de pedidos: acesso somente às tabelas de pedidos
GRANT SELECT, INSERT, UPDATE ON orders TO orders_service;
GRANT SELECT ON products TO orders_service;
-- Não tem acesso à tabela de usuários
-- Serviço de usuários: acesso somente às tabelas de usuários
GRANT SELECT, INSERT, UPDATE ON users TO users_service;
-- Não tem acesso à tabela de pedidos
Prevenção de injeção de SQL
// ❌ Vulnerável a injeção de SQL
const query = `SELECT * FROM users WHERE email = '${email}'`;
// ✅ Consulta parametrizada
const query = 'SELECT * FROM users WHERE email = $1';
const result = await db.query(query, [email]);
Auditoria de acesso
Registre quem acessa quais dados e quando:
CREATE TABLE audit_log (
id SERIAL PRIMARY KEY,
table_name VARCHAR(100),
operation VARCHAR(10), -- SELECT, INSERT, UPDATE, DELETE
user_id VARCHAR(100),
service_name VARCHAR(100),
timestamp TIMESTAMP DEFAULT NOW(),
details JSONB
);
Resumo
A segurança em camadas garante que não exista um único ponto de falha. Cada camada — frontend, API Gateway, BFF, microsserviços e banco de dados — implementa suas próprias defesas. O frontend sanitiza inputs e protege tokens, o Gateway controla o acesso e limita o tráfego, o BFF filtra dados sensíveis, os microsserviços validam tudo e aplicam o menor privilégio, e o banco de dados criptografa e audita. Juntas, essas camadas criam um sistema em que comprometer uma não significa comprometer todas.