Roles & RLS In Depth
O modelo de controle de acesso do PostgreSQL baseia-se em uma única ideia que confunde pessoas de outros bancos de dados: não existe um conceito separado de "usuário" e "grupo".
Busque em todas as páginas da documentação
O modelo de controle de acesso do PostgreSQL baseia-se em uma única ideia que confunde pessoas de outros bancos de dados: não existe um conceito separado de "usuário" e "grupo".
Ambos são o mesmo objeto subjacente, um role, e todo o resto nesta seção, de GRANT à segurança em nível de linha, é construído sobre essa única unificação.
GRANT) e segurança em nível de linha (RLS) respondem a duas perguntas diferentes: "este role pode acessar esta tabela" e "quais linhas dessa tabela ele pode ver", e confundi-las causa lacunas de segurança reais.FORCE ROW LEVEL SECURITY.Documentação mais antiga do PostgreSQL ainda se refere a CREATE USER, mas esse comando é apenas um invólucro de conveniência: ele cria um role com o atributo LOGIN definido, e nada mais.
Um role sem LOGIN se comporta como o que outros sistemas chamam de "grupo", existindo puramente para deter privilégios que outros roles podem herdar.
Esse design de objeto único significa que GRANT app_readers TO app_api e GRANT SELECT ON orders TO app_readers são o mesmo tipo de instrução, apenas concedendo coisas diferentes a um role.
Uma analogia útil é um sistema de crachás em um prédio: um role é um crachá, a associação a outro role é prender um segundo crachá em seu cordão, e INHERIT decide se o leitor de crachás verifica ambos os crachás automaticamente ou apenas o que você apresenta explicitamente.
GRANT, REVOKE e associação de roles juntos decidem quais objetos um role tem permissão para acessar - uma tabela, um esquema, uma função.
A segurança em nível de linha é uma camada separada e posterior que decide quais linhas dentro de um objeto um role tem permissão para ver, depois que ele já passou pela verificação em nível de objeto.
Verificações de privilégio e verificações de RLS ocorrem em pontos diferentes e respondem a perguntas diferentes, que é a distinção mais importante em todo este modelo.
Uma verificação de privilégio em nível de objeto ocorre uma vez, quando o planejador confirma que seu role tem direitos SELECT, INSERT, UPDATE ou DELETE na tabela.
Uma política de segurança em nível de linha, uma vez ativada, é incorporada ao plano de consulta como um filtro implícito, e seu predicado é avaliado por linha em vez de uma vez por instrução.
Isso significa que RLS não é uma permissão no sentido de GRANT; é mais próximo de uma cláusula WHERE injetada automaticamente que o role não pode ver ou remover.
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant_id')::int);USING controla quais linhas existentes são visíveis para SELECT, UPDATE e DELETE; WITH CHECK controla separadamente quais linhas uma linha nova ou modificada tem permissão para se tornar, e omitir WITH CHECK em uma política relevante para UPDATE reutiliza silenciosamente a cláusula USING em vez disso.
O proprietário da tabela e qualquer role com o atributo BYPASSRLS ignoram todas as políticas por padrão, o que é uma escolha de design deliberada para que os roles de migração e administração não sejam acidentalmente bloqueados de suas próprias tabelas.
FORCE ROW LEVEL SECURITY existe especificamente para fechar essa lacuna quando o próprio role proprietário ainda deve estar sujeito às suas próprias políticas, o que é mais importante quando o código do aplicativo é executado como o role proprietário.
Políticas de RLS comumente se baseiam em uma variável de sessão definida uma vez por conexão, como current_setting('app.tenant_id'), que reintroduz silenciosamente a distinção de estado de sessão cliente/servidor coberta em outras partes deste cookbook.
Essa dependência é exatamente por que RLS e pooling de conexões precisam de coordenação deliberada: uma conexão agrupada reutilizada entre tenants sem redefinir essa variável de sessão pode vazar linhas de um tenant para a solicitação de outro tenant.
O desempenho é o outro lugar onde RLS merece uma análise real, porque uma coluna não indexada referenciada na cláusula USING de uma política transforma cada consulta contra essa tabela em um filtro implícito por linha sem um índice para podar.
Indexar as colunas em que suas políticas filtram, tipicamente tenant_id, não é opcional em escala; é a diferença entre RLS não custar nada extra e RLS silenciosamente dobrar a latência da consulta.
Privilégios padrão (ALTER DEFAULT PRIVILEGES) resolvem um problema relacionado, mas distinto: sem eles, cada tabela que uma migração cria precisa ter suas instruções GRANT repetidas manualmente, e é fácil enviar uma nova tabela sem nenhuma concessão, o que falha de forma segura para segurança, mas quebra o aplicativo em tempo de execução.
| Mecanismo | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
GRANT / REVOKE (privilégios de objeto) | Simples, bem compreendido, verificado uma vez por instrução | Tudo ou nada por objeto; não pode restringir a um subconjunto de linhas | Controlar quais tabelas/esquemas um role pode acessar |
| Políticas de segurança em nível de linha | Granular, aplicado mesmo contra consultas ad-hoc | Custo real por linha; invisível a menos que você saiba procurar | Isolamento de linha multi-tenant, dados com escopo de tenant |
Filtragem em nível de aplicativo (WHERE tenant_id = ? no código do app) | Sem custo do lado do banco de dados; mais simples de raciocinar localmente | Apenas tão forte quanto cada caminho de código que lembra o filtro | Ferramentas internas de baixo risco onde um filtro perdido não é catastrófico |
CREATE USER é um invólucro fino em torno de CREATE ROLE ... LOGIN; por baixo, usuários e grupos são o mesmo tipo de objeto.GRANT decide se um role pode acessar uma tabela; RLS decide quais linhas de uma tabela já permitida ele pode ver, e as duas verificações ocorrem em estágios diferentes.FORCE ROW LEVEL SECURITY sujeita o proprietário às suas próprias políticas.WITH CHECK explícito, UPDATE e INSERT reutilizam a cláusula USING, o que muitas vezes não é a restrição que você realmente pretendia para escritas.Não há nenhuma no nível do objeto - CREATE USER apenas cria um role com o atributo LOGIN definido, então "usuário" e "grupo" são ambos instâncias do mesmo objeto ROLE.
A associação (GRANT roleA TO roleB) torna o roleB um membro do roleA, e INHERIT (o padrão) significa que o roleB obtém automaticamente os privilégios do roleA sem precisar de SET ROLE primeiro.
GRANT controla se um role pode acessar um objeto; RLS controla quais linhas específicas dentro de uma tabela já permitida o role pode ver ou modificar.
Não - privilégios de objeto são verificados uma vez por instrução durante o planejamento, enquanto o predicado de uma política RLS é incorporado ao plano e avaliado por linha.
Não por padrão - proprietários e roles com BYPASSRLS ignoram todas as políticas, a menos que a tabela tenha FORCE ROW LEVEL SECURITY definido.
USING filtra quais linhas existentes são visíveis para leituras, atualizações e exclusões; WITH CHECK governa separadamente quais linhas uma escrita pode resultar, e ele usa a expressão USING como padrão se omitido.
Porque a política precisa de algum valor por conexão para comparar com cada linha, e um GUC de sessão definido uma vez no momento da conexão é a maneira padrão de passar esse contexto para a expressão da política.
Sim, se uma conexão agrupada for reutilizada entre tenants sem redefinir a variável de sessão da qual a política depende, o que pode vazar linhas de um tenant para a consulta de outro tenant.
Pode sim, especialmente se a coluna referenciada na política não estiver indexada, já que o predicado então é executado como um filtro não indexado por linha em cada consulta contra essa tabela.
Sem eles, cada nova tabela que uma migração cria começa sem concessões, então um role de aplicativo pode falhar em tempo de execução em uma tabela que foi criada com sucesso, mas nunca explicitamente concedida.
Para ferramentas internas de baixo risco onde um WHERE tenant_id = ? perdido não é catastrófico; onde quer que um filtro perdido seja um incidente de segurança real, a garantia aplicada pelo servidor do RLS vale seu custo.
Apenas quando combinado com o manuseio correto de variáveis de sessão e indexação - RLS aplica a lógica da política corretamente, mas uma variável de sessão vazada ou não definida, ou um predicado não indexado, mina tanto sua segurança quanto seu desempenho.
USING/WITH CHECK em detalheVersões do Stack: Esta página é conceitual e não está vinculada a uma versão específica do stack; as mecânicas de role e segurança em nível de linha descritas aqui são consistentes em versões principais atuais do PostgreSQL, incluindo PostgreSQL 18.4 (linha estável 18, linha de manutenção 17).
Revisado por Chris St. John·Última atualização: 15 de jul. de 2026