行レベルのセキュリティが重要な理由
SQL Server 2016より前は、テーブルレベルのセキュリティがデータベースのデフォルトの最低レベルのセキュリティでした。つまり、ユーザーはテーブル全体へのアクセスを制限される可能性があります。ただし、場合によっては、ユーザーがテーブルにアクセスできる必要がありますが、テーブル内の特定の行にはアクセスできない必要があります。 SQL Server 2016より前は、このようなきめ細かいセキュリティを提供するために、カスタムストアドプロシージャを作成する必要がありました。ただし、このようなストアドプロシージャは、SQLインジェクションやその他のセキュリティ上の警告を受ける傾向があります。
実践でのSQLServer行レベルのセキュリティ機能の使用
SQL Server 2016では、新しい行レベルのセキュリティ機能が導入されました。これにより、ユーザーはテーブルにアクセスできますが、そのテーブル内の特定の行へのアクセスは制限されます。これを実際に使用する方法を見てみましょう。
説明
SQLServerに行レベルのセキュリティを実装するには4つのステップがあります。
- 行レベルのセキュリティを実装するテーブルのユーザーにSelect権限を付与します。
- 次に、フィルター述語を含むインラインテーブル値関数を作成する必要があります。フィルタロジックをフィルタ述語に追加します。
- 最後に、2番目のステップで作成したフィルター述語をセキュリティポリシーにバインドする必要があります。
- 行レベルのセキュリティ機能をテストします。
上記の手順を実行する前に、いくつかのダミーレコードを含むダミーデータベースを作成する必要があります。これを行うには、次のスクリプトを実行します。
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' )
スクリプトでは、ダミーデータベース「University」を作成します。次に、「Persons」という名前のテーブルを作成するスクリプトを実行します。テーブルのデザインを見ると、Id、Name、Roleの3つの列が含まれていることがわかります。 Id列は、IDENTITY制約のある主キー列です。 「名前」列には個人の名前が含まれ、「役割」列にはその個人の役割が含まれます。最後に、Personsテーブルに10個のレコードを挿入しました。テーブルには、校長1人、教師4人、生徒5人がいます。
簡単なSELECTステートメントを実行して、テーブル内のレコードを確認してみましょう。
Use University SELECT * FROM Personsを使用する
結果は次のようになります:
Principalという名前のユーザーが、Personsテーブルのすべての行にアクセスできるようにする必要があります。同様に、教師は教師のレコードのみにアクセスできる必要がありますが、生徒は学生のレコードのみにアクセスできる必要があります。これは、行レベルのセキュリティの典型的なケースです。
行レベルのセキュリティを実装するには、前に説明した手順に従います。
ステップ1:テーブル上のユーザーに選択権限を付与する
Principal、Teacher、Studentの役割を持つ3人のユーザーを作成し、PersonsテーブルでこれらのユーザーにSELECTアクセスを許可しましょう。これを行うには、次のスクリプトを実行します。
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
ステップ2:フィルター述語の作成
ユーザーに権限が付与されたら、次のステップはフィルター述語を作成することです。
次のスクリプトはそれを行います:
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
フィルタ述語は、インラインのテーブル値関数内に作成され、パラメータとしてユーザーの役割を果たします。パラメータとして渡されたRole値がRole列の役割値と一致するレコードを返します。または、ユーザーロールが「プリンシパル」の場合、すべてのロールが返されます。述語フィルターを見ると、フィルターを作成しているテーブル名は見つかりません。フィルタ述語は、次のステップで表示されるセキュリティポリシーを介してテーブルに接続されます。
ステップ3:セキュリティポリシーの作成
次のスクリプトを実行して、前の手順で作成したフィルター述語のセキュリティポリシーを作成します。
Use University Go CREATE SECURITY POLICY RoleFilter ADD FILTER PREDICATE dbo.fn_SP_Person(Role) ON dbo.Persons WITH (STATE = ON); GOを使用する
セキュリティポリシーでは、作成したフィルタ述語をPersonsテーブルに追加しただけです。ポリシーを有効にするには、「STATE」フラグをONに設定する必要があります。
ステップ4:行レベルのセキュリティをテストする
UniversityデータベースのPersonsテーブルに行レベルのセキュリティを適用するために必要なすべての手順を実行しました。まず、デフォルトのユーザーを介してPersonsテーブルのレコードにアクセスしてみましょう。次のスクリプトを実行します。
Use University SELECT * FROM Persons; GOを使用する
デフォルトのユーザーはPersonsテーブルにアクセスできないため、出力には何も表示されません。
以前に作成したStudentユーザーに切り替えて、Personsテーブルからレコードを選択してみましょう:
EXECUTE AS USER = 'Student'; Use University SELECT * FROM Persons; -- Student Records Only REVERT; GO
上記のスクリプトでは、「Student」ユーザーに切り替え、Personsテーブルからレコードを選択して、デフォルトのユーザーに戻します。出力は次のようになります:
行レベルのセキュリティにより、Role列の値がStudentであるレコードのみが表示されていることがわかります。
同様に、ユーザーTeacherは、Role列の値がTeacherであるレコードにのみアクセスできます。次のスクリプトを実行して、これを確認します。
EXECUTE AS USER = 'Teacher'; Use University SELECT * FROM Persons; -- All Records REVERT; GO
出力には、次のレコードが含まれます。
最後に、フィルター述語で、ユーザープリンシパルがすべてのレコードにアクセスできるロジックを実装しました。次のクエリを実行して、これを確認しましょう。
EXECUTE AS USER = 'Principal'; Use University SELECT * FROM Persons; -- All Records REVERT; GO
出力には、以下に示すようにすべてのレコードが表示されます。
結論
行レベルのセキュリティ機能は、ユーザーが特定のデータにきめ細かくアクセスできるようにする場合に非常に役立ちます。ただし、行レベルのセキュリティ機能にはインラインのテーブル値関数が含まれているため、パフォーマンスが低下する可能性があります。
経験則として、述語関数で単純なWHERE句を使用する場合は、パフォーマンスに影響を与えることはありません。一方、行レベルのセキュリティを実装している場合は、ルックアップテーブルを含む複雑な結合ステートメントを避ける必要があります。