【问题标题】:Using EFCore interceptors for row-level security使用 EFCore 拦截器实现行级安全
【发布时间】:2020-06-30 13:51:18
【问题描述】:

我有一个多租户 PostgreSQL 数据库,它使用行级安全性来控制租户应该能够看到的内容。

我正在使用 EF Core 和 ASP.NET Core 来访问这个数据库。为了处理租户访问,我使用连接拦截器来运行适当的数据库命令。例如:

为此,我创建了一个连接拦截器。

public override async Task ConnectionOpenedAsync(
    DbConnection connection, 
    ConnectionEndEventData eventData, 
    CancellationToken cancellationToken = default)
{
    using var command = connection.CreateCommand();
    var commandText = string.Join("\r\n",
        "SET SESSION ROLE TO tenant;",
        $"SET SESSION tenant.userId TO '{this.userId}';");

    command.CommandText = commandText;
    await command.ExecuteNonQueryAsync(cancellationToken);
    await base.ConnectionOpenedAsync(connection, eventData, cancellationToken);
}

所有这些都可以正常工作,但我还需要在非租户上下文中访问数据库。换句话说,我不希望上面的拦截器一直运行。

我正在努力寻找最好的方法来做到这一点。我最初的尝试是使用接口来定义租户或“全局”接口

// (MyDbContext implements both IMyTenantDbContext and IMyGlobalDbContext)
services.AddDbContext<IMyTenantDbContext, MyDbContext>((sp, options) =>
{
    var tenantInterceptor = sp.GetRequiredService<SetTenantConnectionInterceptor>();
    options
        .UseNpgsql(connectionString)
        .AddInterceptors(tenantInterceptor);
});
services.AddDbContext<IMyGlobalDbContext, MyDbContext>(options =>
{
     options
         .UseNpgsql(connectionString);
});

但由于某种原因,DI 系统使用第一个 AddDbContext,即使我尝试注入 IMyGlobalDbContext 意味着仍在使用拦截器。

我确信一定有更好的方法来做到这一点,所以如果我做的完全错误,请告诉我。

【问题讨论】:

    标签: c# entity-framework-core ef-core-3.1


    【解决方案1】:

    我建议将其设置为 userId 可以为 null 或“系统”userId,这意味着不运行拦截器代码,因此同一个拦截器可以处理这两种情况。

    【讨论】:

      【解决方案2】:

      我相信我今天早些时候已经找到了这个问题的答案。我看不到你的 MyDbContext 定义,所以我假设它看起来如何,但我认为它看起来像这样。

      public class MyDbContext : DbContext, IMyTenantDbContext, IMyGlobalDbContext
      {
          public MyDbContext()
          {
          }
      
          public MyDbContext(DbContextOptions<MyDbContext> options)
              : base(options)
          {
          }
      }
      

      如果是这样,我认为问题出在构造函数中注入的DbContextOptions&lt;MyDbContext&gt; options 参数中。当您请求 IMyTenantDbContextIMyGlobalDbContext 时,您正在注入 DbContext,但是,EntityFramework 正在为 DbContextOptions&lt;&gt; 设置 DI,并且每次您使用其中一个接口构建 DbContext 时,您'每次都注入相同的选项。最终结果是,相同的选项集被传递给您以这种方式创建的任何上下文。

      这个问题有一个两步的解决方案。第 1 步,将其改为基于继承的架构,如下所示:

      public class MyTenantDbContext : MyDbContext
      {
          public MyTenantDbContext ()
          {
          }
      
          public MyTenantDbContext (DbContextOptions<MyDbContext> options)
              : base(options)
          {
          }
      }
      
      public class MyDbContext : DbContext
      {
          public MyDbContext()
          {
          }
      
          public MyDbContext(DbContextOptions<MyDbContext> options)
              : base(options)
          {
          }
      }
      

      基本上,只需在您的 MyDbContext 周围创建一个包装器。

      这只是解决方案的 1/2,因为我们仍然在两个类中使用 DbContextOptions&lt;MyDbContext&gt;,所以我们仍然会在每个类中获得相同的选项集。由于对我们可以从MyTenantDbContext 类构造函数传递给MyDbContext 基类的选项的限制,我们不能只传递DbContextOptions&lt;MyTenantDbContext&gt; 对象。这在这个 github issue 中有详细解释。 https://github.com/dotnet/efcore/issues/7533

      在同一个 github 问题中也提到了该问题的修复(以及解决方案的第 2 步)。即,在接受非泛型 DbContextOptions 类的基类上创建一个受保护的构造函数。那么你可以将MyTenantDbContext构造函数中的类型更改为DbContextOptions&lt;MyTenantDbContext&gt;

      这是最终代码。

      public class MyTenantDbContext : MyDbContext
      {
          public MyTenantDbContext()
          {
          }
      
          public MyTenantDbContext(DbContextOptions<MyTenantDbContext> options)
              : base(options)
          {
          }
      }
      
      public class MyDbContext : DbContext
      {
          public MyDbContext()
          {
          }
      
          public MyDbContext(DbContextOptions<MyDbContext> options)
              : base(options)
          {
          }
      
          protected MyDbContext(DbContextOptions options)
              : base(options)
          {
          }
      }
      

      当您为此设置 DI 时,为这两种具体类型设置上下文,DbContextOptions 将通过正确的配置传递给正确的上下文类。

      【讨论】:

        猜你喜欢
        • 2021-03-25
        • 2014-11-27
        • 2019-04-27
        • 1970-01-01
        • 2020-01-21
        • 2021-05-20
        • 2013-05-06
        • 2020-03-03
        • 2020-12-16
        相关资源
        最近更新 更多