我尝试通过 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”。
这段代码适用于我在 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>();
我在尝试对从 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);
}
....
}