Accès ligne par ligne à SQL Server

10

J'ai un tableau structuré comme ça (simplifié)

Name, EMail, LastLoggedInAt

J'ai un utilisateur dans SQL Server (RemoteUser) qui ne devrait pouvoir voir que les données (via une requête de sélection) où le champ LastLoggdInAt n'est pas nul.

On dirait que je peux faire ça? C'est possible?

LiamB
la source
Voici la rubrique de documentation en ligne pour la sécurité au niveau des lignes
David Browne - Microsoft

Réponses:

32

Le modèle de sécurité SQL Server vous permet d'accorder l'accès à une vue sans accorder l'accès aux tables sous-jacentes.

Étant donné que l'exemple de code est un excellent moyen de montrer un concept, considérez ce qui suit, avec une LoginDetailstable et la vue correspondante:

CREATE TABLE dbo.LoginDetails
(
    Username nvarchar(100) NOT NULL
    , EmailAddress nvarchar(256) NOT NULL
    , LastLoggedInAt datetime NULL
);
GO

CREATE VIEW dbo.LoginDetailsView
AS
SELECT ld.Username
    , ld.EmailAddress
    , ld.LastLoggedInAt
FROM dbo.LoginDetails ld
WHERE ld.LastLoggedInAt IS NOT NULL;
GO

Nous allons créer une connexion et un utilisateur, puis attribuer à cet utilisateur les droits pour sélectionner des lignes dans la vue, sans avoir le droit de visualiser la table elle-même.

CREATE LOGIN RemoteUser 
WITH PASSWORD = '2q1345lkjsadfgsa0(*';

CREATE USER RemoteUser
FOR LOGIN RemoteUser
WITH DEFAULT_SCHEMA = dbo;

GRANT SELECT ON dbo.LoginDetailsView TO RemoteUser;

Maintenant, nous allons insérer deux lignes de test:

INSERT INTO dbo.LoginDetails(Username, EmailAddress, LastLoggedInAt)
VALUES ('user x', '[email protected]', NULL)
    , ('user y', '[email protected]', GETDATE());

Cela teste le modèle de sécurité. La première SELECTinstruction réussit, car elle sélectionne dans la vue, tandis que la deuxième SELECTinstruction échoue parce que l'utilisateur n'a pas d'accès direct à la table.

EXECUTE AS LOGIN = 'RemoteUser';

SELECT *
FROM dbo.LoginDetailsView;
╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nom d'utilisateur ║ EmailAddress ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ utilisateur y ║ [email protected] ║ 2018-02-15 07: 36: 54.490 ║
╚══════════╩══════════════╩═══════════════════════ ══╝
SELECT *
FROM dbo.LoginDetails;

REVERT

Notez que les résultats de la vue excluent la ligne où se trouve la LastLoggedInAtvaleur NULL, comme requis dans votre question.

La deuxième SELECTinstruction sur la table sous-jacente renvoie une erreur:

Msg 229, niveau 14, état 5, ligne 28
L'autorisation SELECT a été refusée sur l'objet 'LoginDetails', base de données 'tempdb', schéma 'dbo'.

Nettoyer:

DROP USER RemoteUser;
DROP LOGIN RemoteUser;
DROP VIEW dbo.LoginDetailsView;
DROP TABLE dbo.LoginDetails;

Alternativement, si vous avez SQL Server 2016 ou une version plus récente, vous pouvez utiliser un prédicat de sécurité de niveau ligne pour empêcher certains utilisateurs de voir des lignes avec une LastLoggedInAtvaleur NULL .

Tout d'abord, nous créons la table, une connexion, un utilisateur pour cette connexion, et nous accordons l'accès à la table:

CREATE TABLE dbo.LoginDetails
(
    Username nvarchar(100) NOT NULL
    , EmailAddress nvarchar(256) NOT NULL
    , LastLoggedInAt datetime NULL
);
GO

CREATE LOGIN RemoteUser 
WITH PASSWORD = '2q1345lkjsadfgsa0(*';

CREATE USER RemoteUser
FOR LOGIN RemoteUser
WITH DEFAULT_SCHEMA = dbo;

GRANT SELECT ON dbo.LoginDetails TO RemoteUser;

Ensuite, nous insérons quelques exemples de lignes. Une ligne avec une valeur nulle LastLoggedInAtet une avec une valeur non nulle pour cette colonne.

INSERT INTO dbo.LoginDetails(Username, EmailAddress, LastLoggedInAt)
VALUES ('user x', '[email protected]', NULL)
    , ('user y', '[email protected]', GETDATE());

Ici, nous créons une fonction table de valeur liée au schéma qui renvoie une ligne avec 0 ou 1 selon la valeur des variables @LastLoggedInAtet @usernamequi sont passées dans la fonction. Cette fonction sera utilisée par un prédicat de filtre pour éliminer les lignes que nous voulons cacher à certains utilisateurs.

CREATE FUNCTION dbo.fn_LoginDetailsRemoteUserPredicate
(
    @LastLoggedInAt datetime
    , @username sysname
)  
RETURNS TABLE  
WITH SCHEMABINDING  
AS  
    RETURN SELECT 1 AS fn_securitypredicate_result   
    WHERE (@username = N'RemoteUser' AND @LastLoggedInAt IS NOT NULL)
        OR @username <> N'RemoteUser';  
GO

Il s'agit du filtre de sécurité qui élimine les lignes des SELECTinstructions exécutées sur la dbo.LoginDetailstable:

CREATE SECURITY POLICY LoginDetailsRemoteUserPolicy
ADD FILTER PREDICATE dbo.fn_LoginDetailsRemoteUserPredicate(LastLoggedInAt, USER_NAME())
ON dbo.LoginDetails
WITH (STATE=ON);

Le filtre ci-dessus utilise la dbo.fn_LoginDetailsRemoteUserPredicatefonction en transmettant le nom de l'utilisateur actuel, ainsi que les valeurs de chaque ligne pour la LastLoggedInAtcolonne de la dbo.LoginDetailstable.

Si nous interrogeons la table en tant qu'utilisateur normal:

SELECT *
FROM dbo.LoginDetails

nous voyons toutes les lignes:

╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nom d'utilisateur ║ EmailAddress ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ utilisateur x ║ [email protected] ║ NULL ║
║ utilisateur y ║ [email protected] ║ 2018-02-15 13: 53: 42.577 ║
╚══════════╩══════════════╩═══════════════════════ ══╝

Cependant, si nous testons comme RemoteUser:

EXECUTE AS LOGIN = 'RemoteUser';

SELECT *
FROM dbo.LoginDetails

REVERT

nous ne voyons que les lignes "valides":

╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nom d'utilisateur ║ EmailAddress ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ utilisateur y ║ [email protected] ║ 2018-02-15 13: 42: 02.023 ║
╚══════════╩══════════════╩═══════════════════════ ══╝

Et nous nettoyons:

DROP SECURITY POLICY LoginDetailsRemoteUserPolicy;
DROP FUNCTION dbo.fn_LoginDetailsRemoteUserPredicate;
DROP USER RemoteUser;
DROP LOGIN RemoteUser;
DROP TABLE dbo.LoginDetails;

N'oubliez pas que la liaison de schéma d'une fonction à la table de cette manière ne permet pas de modifier la définition de la table sans supprimer au préalable le prédicat de filtre et la dbo.fn_LoginDetailsRemoteUserPredicatefonction.

Max Vernon
la source
Réponse brillante - merci! Quelle est l'implication des performances de ces 2 méthodes. Nous avons constaté que notre application Web est jusqu'à 5 fois plus lente lorsque nous utilisons la fonction. Besoin de regarder la méthode d'affichage.
LiamB
La fonction de sécurité au niveau des lignes est évaluée pour chaque ligne lue dans le tableau; Je m'attends à ce qu'il ralentisse considérablement l'accès à cette table. La vue, en revanche, devrait avoir un impact négligeable sur les performances en supposant que vous créez un index utile sur la LastLoggedInAtcolonne.
Max Vernon
Cela a du sens - je vais regarder la vue maintenant, semble bien fonctionner! Si nous voulions que l'utilisateur puisse uniquement modifier les données utilisateur pour ces lignes qui correspondent aux critères, cela serait-il possible avec la vue?
LiamB
C'est beau, tout fonctionne - merci pour l'aide avec cela
LiamB
Oui, vous pouvez modifier les lignes via la vue
Max Vernon