Por que a segurança no nível da linha é importante?
Antes do SQL Server 2016, a segurança em nível de tabela era o nível de segurança mais baixo padrão para um banco de dados. Em outras palavras, um usuário pode ter o acesso restrito a uma tabela como um todo. No entanto, em alguns casos, precisamos que os usuários tenham acesso a uma tabela, mas não a linhas específicas da tabela. Antes do SQL Server 2016, isso exigia que procedimentos armazenados personalizados fossem escritos para o fornecimento dessa segurança refinada. No entanto, esses procedimentos armazenados são propensos a injeção de SQL e outras ressalvas de segurança.
Usando o recurso de segurança em nível de linha do SQL Server na prática
O SQL Server 2016 introduziu um novo recurso de segurança em nível de linha que permite que os usuários tenham acesso a uma tabela, mas os restringe a acessar linhas específicas nessa tabela. Vamos dar uma olhada em como isso pode ser usado na prática.
Descrição
Há quatro etapas para implementar a segurança em nível de linha no SQL Server.
- Conceda permissões de seleção aos usuários na tabela na qual você deseja implementar a segurança em nível de linha.
- Em seguida, você deve escrever uma função de valor de tabela inline contendo um predicado de filtro. Adicione a lógica do filtro ao predicado do filtro.
- Por fim, você precisa vincular o predicado de filtro criado na segunda etapa a uma política de segurança.
- Teste o recurso de segurança em nível de linha.
Antes de realizarmos as etapas acima, precisamos criar um banco de dados fictício com alguns registros fictícios. Execute o seguinte script para fazer isso:
CREATE DATABASE University GO USE University GO USE University CREATE TABLE Persons ( Id INT PRIMARY KEY IDENTITY(1,1), Name VARCHAR (50), Role VARCHAR (50) ) GO USE University INSERT INTO Persons VALUES ('Sally', 'Principal' ) INSERT INTO Persons VALUES ('Edward', 'Student' ) INSERT INTO Persons VALUES ('Jon', 'Student' ) INSERT INTO Persons VALUES ('Scot', 'Student') INSERT INTO Persons VALUES ('Ben', 'Student' ) INSERT INTO Persons VALUES ('Isabel', 'Teacher' ) INSERT INTO Persons VALUES ('David', 'Teacher' ) INSERT INTO Persons VALUES ('Laura', 'Teacher' ) INSERT INTO Persons VALUES ('Jean', 'Teacher') INSERT INTO Persons VALUES ('Francis', 'Teacher' )
No script, criamos um banco de dados fictício “Universidade”. Em seguida, executamos o script que cria uma tabela chamada “Persons”. Se você observar o design da tabela, verá que ele contém três colunas Id, Name e Role. A coluna Id é a coluna de chave primária com restrição IDENTITY. A coluna Nome contém o nome da pessoa e a coluna Função contém a função da pessoa. Por fim, inserimos 10 registros na tabela Persons. A mesa tem 1 diretor, 4 professores e 5 alunos.
Vamos executar uma instrução SELECT simples para ver os registros na tabela:
Use University SELECT * FROM Persons
O resultado fica assim:
Queremos que o usuário chamado Principal tenha acesso a todas as linhas da tabela Persons. Da mesma forma, um Professor deve ter acesso apenas aos registros do Professor, enquanto os Alunos devem ter acesso apenas aos registros do Aluno. Este é um caso clássico de segurança em nível de linha.
Para implementar a segurança em nível de linha, seguiremos as etapas que discutimos anteriormente.
Etapa 1:conceder permissões de seleção aos usuários na mesa
Vamos criar três usuários com funções Principal, Teacher e Student e conceder a eles acesso SELECT a esses usuários na tabela Persons. Execute o seguinte script para fazer isso:
CREATE USER Principal WITHOUT LOGIN; GO CREATE USER Teacher WITHOUT LOGIN; GO CREATE USER Student WITHOUT LOGIN; GO Use University GRANT SELECT ON Persons TO Principal; GO GRANT SELECT ON Persons TO Teacher; GO GRANT SELECT ON Persons TO Student; GO
Etapa 2:criando predicado de filtro
Depois que os usuários obtiverem permissões, a próxima etapa é criar um predicado de filtro.
O script a seguir faz isso:
Use University GO CREATE FUNCTION dbo.fn_SP_Person(@Role AS sysname) RETURNS TABLE WITH SCHEMABINDING AS RETURN SELECT 1 AS fn_SP_Person_output -- Predicate logic WHERE @Role = USER_NAME() OR USER_NAME() = 'Principal'; GO
O predicado de filtro é criado dentro de uma função com valor de tabela embutido e assume o papel do usuário como parâmetro. Ele retorna os registros em que o valor da função passado como parâmetro corresponde ao valor da função na coluna Função. Ou se a função do usuário for 'Principal', todas as funções serão retornadas. Se você observar o filtro de predicado, não encontrará o nome da tabela para a qual estamos criando o filtro. O predicado do filtro é conectado à tabela por meio da política de segurança que veremos na próxima etapa.
Etapa 3:criando uma política de segurança
Execute o script a seguir para criar uma política de segurança para o predicado de filtro que criamos na última etapa:
Use University Go CREATE SECURITY POLICY RoleFilter ADD FILTER PREDICATE dbo.fn_SP_Person(Role) ON dbo.Persons WITH (STATE = ON); GO
Na política de segurança, simplesmente adicionamos o predicado de filtro que criamos à tabela Persons. Para habilitar a política, o sinalizador “STATE” deve ser definido como ON.
Etapa 4:testando a segurança em nível de linha
Executamos todas as etapas necessárias para impor a segurança em nível de linha na tabela Persons do banco de dados da Universidade. Vamos primeiro tentar acessar os registros na tabela Persons via usuário padrão. Execute o seguinte script:
Use University SELECT * FROM Persons; GO
Você não verá nada na saída, pois o usuário padrão não pode acessar a tabela Persons.
Vamos mudar para o usuário Student que criamos anteriormente e tentar SELECT registros da tabela Persons:
EXECUTE AS USER = 'Student'; Use University SELECT * FROM Persons; -- Student Records Only REVERT; GO
No script acima, mudamos para o usuário ‘Student’, selecionamos os registros da tabela Persons e voltamos para o usuário padrão. A saída fica assim:
Você pode ver que, devido à segurança em nível de linha, apenas os registros em que a coluna Função tem um valor de Aluno são exibidos.
Da mesma forma, o usuário Professor só terá acesso aos registros onde a coluna Função tiver o valor Professor. Execute o script a seguir para verificar isso:
EXECUTE AS USER = 'Teacher'; Use University SELECT * FROM Persons; -- All Records REVERT; GO
Na saída, você terá os seguintes registros:
Por fim, em nosso predicado de filtro, implementamos a lógica de que o usuário Principal pode acessar todos os registros. Vamos verificar isso executando a seguinte consulta:
EXECUTE AS USER = 'Principal'; Use University SELECT * FROM Persons; -- All Records REVERT; GO
Na saída, você verá todos os registros conforme mostrado abaixo:
Conclusão
O recurso de segurança em nível de linha é extremamente útil quando você deseja que os usuários tenham acesso refinado a dados específicos. No entanto, o recurso de segurança em nível de linha envolve função com valor de tabela embutido, o que pode fazer com que você sofra um impacto no desempenho.
Como regra geral, se você planeja usar uma cláusula WHERE simples na função de predicado, seu desempenho não deve ser afetado. Por outro lado, instruções de junção complexas envolvendo tabelas de pesquisa devem ser evitadas quando você implementou a segurança em nível de linha.