天天看點

SaaS 多租戶架構持久層的設計(四)SQL資料庫的資料隔離

作者:芝士Flash

之前有朋友提到資料的隔離,這裡先介紹一種傳統SQL的隔離:RLS的隔離。

SaaS 多租戶架構持久層的設計(四)SQL資料庫的資料隔離

1. 應用層:在SESSION_CONTEXT中設定 TenantId

首先,使用資料庫用戶端庫的資料相關路由 API 連接配接到分片資料庫。應用程式仍必須告知資料庫哪個 TenantId 正在使用連接配接。TenantId 告知 RLS 安全政策哪些行必須篩選為屬于其他租戶。将目前租戶 ID 存儲在連接配接的SESSION_CONTEXT中。

SESSION_CONTEXT的替代方法是使用CONTEXT_INFO。但SESSION_CONTEXT是更好的選擇。SESSION_CONTEXT更易于使用,但它預設傳回 NULL,并且支援鍵值對。zai

在傳回connection前把SESSION_CONTEXT附加到connection上:

cmd.CommandText =
            @"exec sp_set_session_context
                @key=N'TenantId', @value=@shardingKey";
        cmd.Parameters.AddWithValue("@shardingKey", shardingKey);
        cmd.ExecuteNonQuery();           

2. 資料層:建立行級别安全政策

建立安全政策以篩選每個租戶可以通路的行

現在,應用程式在查詢之前使用目前 TenantId 設定SESSION_CONTEXT,RLS 安全政策可以篩選查詢并排除具有不同 TenantId 的行。

RLS 在 Transact-SQL 中實作。使用者定義的函數定義通路邏輯,安全政策将此函數綁定到任意數量的表。對于此項目:

  1. 該函數驗證應用程式是否已連接配接到資料庫,以及存儲在SESSION_CONTEXT中的 TenantId 是否與給定行的 TenantId 比對。
  2. 應用程式已連接配接,而不是其他某個 SQL 使用者。
  3. 篩選器謂詞允許滿足 TenantId 篩選器的行通過選擇、更新和删除查詢。
  4. BLOCK 謂詞可防止未通過篩選器的行入或更新。
  5. 如果尚未設定SESSION_CONTEXT,則該函數傳回 NULL,并且沒有行可見或無法插入。

若要在所有分片上啟用 RLS,可以看一下此 T-SQL。建立一個policy 去過濾資料。

CREATE SCHEMA rls; -- Separate schema to organize RLS objects.
GO

CREATE FUNCTION rls.fn_tenantAccessPredicate(@TenantId int)
    RETURNS TABLE
    WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS fn_accessResult
        -- Use the user in your application's connection string.
        -- Here we use 'dbo' only for demo purposes!
        WHERE DATABASE_PRINCIPAL_ID() = DATABASE_PRINCIPAL_ID('dbo')
        AND CAST(SESSION_CONTEXT(N'TenantId') AS int) = @TenantId;
GO

CREATE SECURITY POLICY rls.tenantAccessPolicy
    ADD FILTER PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Blogs,
    ADD BLOCK  PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Blogs,
    ADD FILTER PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Posts,
    ADD BLOCK  PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Posts;
GO           

如果稍後添加新表,請更改安全政策以在新表上添加 FILTER 和 BLOCK 謂詞。

ALTER SECURITY POLICY rls.tenantAccessPolicy
    ADD FILTER PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.MyNewTable,
    ADD BLOCK  PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.MyNewTable;
GO           

添加預設限制以自動填充插入的租戶 ID

可以在每個表上放置預設限制,以便在插入行時使用目前存儲在SESSION_CONTEXT中的值自動填充 TenantId。下面是一個示例。

-- Create default constraints to auto-populate TenantId with the
-- value of SESSION_CONTEXT for inserts.
ALTER TABLE Blogs
    ADD CONSTRAINT df_TenantId_Blogs
    DEFAULT CAST(SESSION_CONTEXT(N'TenantId') AS int) FOR TenantId;
GO

ALTER TABLE Posts
    ADD CONSTRAINT df_TenantId_Posts
    DEFAULT CAST(SESSION_CONTEXT(N'TenantId') AS int) FOR TenantId;
GO           

繼續閱讀