使用 EF 将 HttpContext(或只是用户信息)获取到 .Net Core 6 中的 DbConnectionInterceptor 中

问题描述 投票:0回答:2

我尝试通过 DbConnectionInterceptor 在连接到我的 SQL Server 数据库的位置运行 sp_set_session_context 来设置 RLS。当我使用硬编码的用户名时,它工作得非常好,但我确实需要从User.Identity.Name中提取用户的信息。我似乎找不到将 HttpContext 传递到我的 DbConnectionInterceptor 的方法。

我尝试的一种方法是像这样使用 DI:

public class EFConnectionInterceptor : DbConnectionInterceptor, IDbConnectionInterceptor
{
    private readonly IHttpContextAccessor _context;
    public EFConnectionInterceptor(IHttpContextAccessor context)
    {
        _context = context;
    }

    public override Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData eventData, CancellationToken cancellationToken = default)
    {
        var Username = _context.HttpContext?.User?.Identity?.Name;
        if (Username != null)
        {
            DbCommand cmd = connection.CreateCommand();
            cmd.CommandText = "EXEC sp_set_session_context @key=N'Username', @value=@Username";
            DbParameter param = cmd.CreateParameter();
            param.ParameterName = "@Username";
            param.Value = Username;
            cmd.Parameters.Add(param);
            cmd.ExecuteNonQuery();
        }
        return Task.CompletedTask;
    }
}

然后用我的拦截器在我的 Progam.cs 中调用 AddDbContext:

builder.Services.AddHttpContextAccessor();
builder.Services.AddDbContext<MyContext>(options =>
  options
    .UseSqlServer(builder.Configuration.GetConnectionString("myconnstring"))
    .AddInterceptors(new EFConnectionInterceptor())
    );

但这无法编译,因为它需要我将 HttpContext 传递到 new EFConnectionInterceptor() 调用中。我目前似乎无法从提供商处访问 GetRequiredService(),因为我还没有构建该服务,所以我不知道如何在这里获取它。

如何将用户名输入到我的拦截器中?

完全不同的方法也可以,只要我在访问任何数据库资源之前使用用户名有效地调用 sp_set_session_context 即可。

我正在使用数据库优先方法和Scaffold-DbContext数百个数据库表拉入项目中,因此我不想修改我的DbContext(上面名为MyContext)。

拦截器是一种非常诱人的方法,但我就是无法将用户信息放入其中!

注意: 我确实看到了这个帖子: 如何从请求中的授权令牌获取用户 ID,以便在 DbConnectionInterceptor 中使用它? 但它与中间件解决方案有同样的问题,因为我不能这样做:

services.AddDbContext<ApplicationDbContext>((provider, options) =>
{
    // [...]
    options.AddInterceptors(provider.GetRequiredService<DbUserIdInterceptor>());
});

因为 (provider,options) 在 .net Core 6 中无法按照我在 Program.cs 中构建的方式工作——我在构建时没有“provider”。

c# entity-framework-6 interceptor asp.net-core-6.0 row-level-security
2个回答
0
投票

这段代码适用于我在 Asp.net Core7 中

 public partial class PlanContext : DbContext
{
    public PlanContext()
    {
    }

    public PlanContext(DbContextOptions<PlanContext> options)
        : base(options)
    {
    }

    public virtual DbSet<Tplan> Tplans { get; set; }

    public virtual DbSet<Tplanlog> Tplanlogs { get; set; }

    public static string UNAME { get; set; }
    private readonly IHttpContextAccessor _contextAccessor;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=serv1;Database=Db1;User ID=U1; Password=Pass; TrustServerCertificate=True");
        optionsBuilder.AddInterceptors(new AppDbConnectionInterceptor());
        UNAME = _contextAccessor.HttpContext.User.Identity.Name;
    }

    public PlanContext(DbContextOptions<PlanContext> options, IHttpContextAccessor contextAccessor)
: base(options)
    {
        _contextAccessor = contextAccessor;
    }

    public class AppDbConnectionInterceptor : DbConnectionInterceptor
    {
        public AppDbConnectionInterceptor() { }
        public override async Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData eventData,  CancellationToken cancellationToken = default)
        {
            var Logonuser = UNAME; 
            var keyParameter = new SqlParameter("Key", Logonuser);
            var sqlCommand = new SqlCommand("EXEC sp_set_session_context 'UNAME', @key;", (SqlConnection)connection);
            sqlCommand.Parameters.Add(keyParameter);
            await sqlCommand.ExecuteNonQueryAsync();
        }
    }

/////////////

}

程序.cs

builder.Services.AddSingleton<Microsoft.AspNetCore.Http.IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();

0
投票

我在尝试对从 Blazor Server 发送到 Azure SQL 的请求进行身份验证时遇到了同样的问题。我通过创建一个新的

HttpContextAccessor
(没有将其添加到 DI 容器)并将其传递给我的
DbConnectionInterceptor
来获得它。

// .NET 6/7/8 Program.cs
....

string[] scopes = new string[] { "https://database.windows.net/user_impersonation", "User.Read" };
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration)
    .EnableTokenAcquisitionToCallDownstreamApi(scopes)
    .AddInMemoryTokenCaches();

....

// Create a new HttpContextAccessor but don't add it to the DI container.
var httpContextAccessor = new HttpContextAccessor();
builder.Services.AddDbContextFactory<AutotaskDbContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("SqlConnStr"));
    options.AddInterceptors(
        new AadAuthDbConnectionInterceptor(() =>
        {
            return httpContextAccessor.HttpContext.GetTokenAsync("access_token").Result;
        })
    );
});
public class AadAuthDbConnectionInterceptor : DbConnectionInterceptor
{
    protected Func<string> _getToken;

    public AadAuthDbConnectionInterceptor(Func<string> getToken)
    {
        _getToken = getToken;
    }

    public override InterceptionResult ConnectionOpening(
        DbConnection connection,
        ConnectionEventData eventData,
        InterceptionResult result)
    {
        var sqlConnection = (SqlConnection)connection;
        if (DoesConnectionNeedAccessToken(sqlConnection))
        {
            // Use the access token to authenticate the connection.
            sqlConnection.AccessToken = _getToken.Invoke();
        }

        return base.ConnectionOpening(connection, eventData, result);
    }

    ....
}
© www.soinside.com 2019 - 2024. All rights reserved.