Como implementar Row-Level Security no SQL Server
Row-Level Security (RLS) no SQL Server permite que cada utilizador veja apenas as linhas de uma tabela que lhe dizem respeito, sem duplicar tabelas nem alterar o código das aplicações. É uma peça central da governança de dados, porque a regra de acesso fica guardada na própria base de dados e aplica-se automaticamente a qualquer consulta — venha ela de um relatório em Power BI, de uma API ou do SQL Server Management Studio. Neste passo a passo vais criar uma política de RLS do zero e confirmá-la com utilizadores diferentes.
Pré-requisitos
- SQL Server 2016 ou superior, ou uma base de dados no Azure SQL Database.
- Uma ferramenta de consulta, como o SQL Server Management Studio (SSMS) ou o Azure Data Studio.
- Permissões para criar tabelas, funções e políticas de segurança (por exemplo, a função db_owner numa base de dados de testes).
- Conhecimentos básicos de T-SQL, como CREATE TABLE e SELECT.
A ideia por trás da RLS é simples e assenta em duas peças: uma função de predicado, que decide se uma linha é visível, e uma política de segurança, que aplica essa função a uma tabela. Vamos construir as duas.
Passo 1: Criar uma tabela de exemplo
Começamos com uma tabela de vendas em que cada linha pertence a um comercial. A coluna SalesRep guarda o nome do utilizador dono da linha; é essa coluna que a política vai usar para filtrar.
CREATE TABLE dbo.Sales
(
SaleId INT IDENTITY PRIMARY KEY,
SalesRep SYSNAME,
Product NVARCHAR(50),
Amount DECIMAL(10,2)
);
INSERT INTO dbo.Sales (SalesRep, Product, Amount) VALUES
('ana', 'Teclado', 120.00),
('bruno', 'Monitor', 240.00),
('ana', 'Rato', 45.00);
Passo 2: Criar utilizadores para testar
Para ver a RLS em ação precisamos de utilizadores diferentes. Criamos dois utilizadores sem login — chegam para a demonstração — e damos-lhes permissão de leitura sobre a tabela.
CREATE USER ana WITHOUT LOGIN;
CREATE USER bruno WITHOUT LOGIN;
GRANT SELECT ON dbo.Sales TO ana, bruno;
Passo 3: Criar a função de predicado
A regra de acesso vive numa função inline com valor de tabela (inline table-valued function). Ela devolve o valor 1 quando a linha deve aparecer para o utilizador. Neste exemplo, uma linha é visível se o valor de SalesRep for igual ao nome do utilizador atual, obtido com USER_NAME(). Guardamos a função num schema próprio, chamado Security, por uma questão de organização e boas práticas.
CREATE SCHEMA Security;
GO
CREATE FUNCTION Security.fn_vendasPorComercial(@SalesRep AS SYSNAME)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS resultado
WHERE @SalesRep = USER_NAME() OR USER_NAME() = 'dbo';
GO
A cláusula WITH SCHEMABINDING é obrigatória para funções usadas em RLS. A condição extra USER_NAME() = 'dbo' garante que o administrador continua a ver todas as linhas, o que é prático para tarefas de gestão e manutenção.
Passo 4: Criar a política de segurança
Falta ligar a função à tabela. Uma SECURITY POLICY com um FILTER PREDICATE aplica a função à coluna SalesRep da tabela dbo.Sales. Com STATE = ON, a política fica ativa de imediato.
CREATE SECURITY POLICY Security.VendasFiltro
ADD FILTER PREDICATE Security.fn_vendasPorComercial(SalesRep)
ON dbo.Sales
WITH (STATE = ON);
Um FILTER PREDICATE esconde as linhas nas leituras (SELECT, UPDATE, DELETE). Existe também o BLOCK PREDICATE, que impede que um utilizador insira ou altere linhas para fora do seu âmbito — útil quando queres proteger também a escrita.
Verificar o resultado
Agora podemos consultar a tabela fingindo ser cada utilizador, com EXECUTE AS. A comercial "ana" deve ver apenas as suas duas linhas e "bruno" apenas a dele.
EXECUTE AS USER = 'ana';
SELECT * FROM dbo.Sales; -- devolve as 2 linhas da ana
REVERT;
EXECUTE AS USER = 'bruno';
SELECT * FROM dbo.Sales; -- devolve a 1 linha do bruno
REVERT;
Se cada utilizador vir apenas as suas linhas, a RLS está a funcionar. Repara que a consulta é exatamente a mesma nos dois casos: a filtragem acontece de forma transparente, sem ninguém precisar de acrescentar um WHERE. É esta transparência que torna a RLS tão útil — a regra fica no sítio certo, a base de dados, e não espalhada por dezenas de relatórios.
Conclusão
Com uma função de predicado e uma política de segurança passaste a controlar o acesso aos dados linha a linha, diretamente no SQL Server. A partir daqui podes tornar a regra mais dinâmica — por exemplo, usar SESSION_CONTEXT para identificar o utilizador da aplicação em cenários multi-tenant — ou reforçar a escrita com um BLOCK PREDICATE. Para desligar a política durante um teste, basta ALTER SECURITY POLICY Security.VendasFiltro WITH (STATE = OFF). Que outra tabela do teu modelo beneficiaria de uma regra de acesso ao nível da linha?