SQL Server模拟和连接池

Cri*_*ole 10 sql-server impersonation

我已经被赋予了为遗留数据库编写Web界面的任务,我们拥有所有用户都拥有数据库帐户并相应地分配角色(当用户执行某些操作时,我们都会在整个地方记录触发器,所有这些都基于user_name()).

为了使用任何远程现代的东西,并避免以纯文本形式存储用户的密码,我正在连接具有每个用户的模拟权限的应用程序级帐户,我正在尝试运行Execute As User=@username并Revert设置和重置执行运行任何SQL之前和之后的上下文.

不幸的是,连接池的reset_connection呼叫与我的连接混乱,并且它最终会抛出一些关于物理连接无效的令人讨厌的错误...

我可以通过不使用连接池来解决此错误.但是,我的应用程序用户需要疯狂的权限来实际执行模拟.此外,杀死连接池是一个无赖......

如何在不牺牲安全性或性能的情况下做到这一点?请记住,我无法改变我的用户拥有数据库登录的事实,而且我真的不会以可检索的方式存储用户密码.我是绕过连接池的唯一选择,因此我可以冒充(并使用sa用户,因此我有足够的权限来实际冒充某人)?

Fil*_*Vos 2

为了实现一种“假”委托而不需要对应用程序/数据库代码进行巨大更改,我建议使用context_info()将当前用户传输到数据库,并将对 的调用替换为user_name()对dbo.fn_user_name().

有关如何构建此解决方案的示例

创建 fn_user_name() 函数

我将创建一个函数 fn_user_name ,它将从连接上的 context_info() 中提取用户名,或者在没有可用上下文信息时返回 user_name() 。请注意,连接上下文是 128 字节的二进制文件。你放在那里的任何内容都将用零字符填充,为了解决这个问题,我用空格填充值。

create function dbo.fn_user_name()
returns sysname
as
begin
    declare @user sysname = rtrim(convert(nvarchar(64), context_info()))
    if @user is null 
        return user_name()
    return @user
end
go
Run Code Online (Sandbox Code Playgroud)

现在您会发现替换代码中对 user_name() 的所有调用并将其替换为此函数。

将上下文嵌入到 .net 中的数据库调用中

这里有 2 个选项。或者您创建自己的 SqlConnection 类,或者创建一个工厂方法,该方法将返回一个打开的 sqlconnection,如下所示。工厂方法有一个问题,即您运行的每个查询都将是 2 个数据库调用。不过,这是要编写的最少代码。

    public SqlConnection CreateConnection(string connectionString, string user)
    {
        var conn = new SqlConnection(connectionString);
        using (var cmd = new SqlCommand(
            @"declare @a varbinary(128) = convert(varbinary(128), @user + replicate(N' ', 64 - len(@user)))
              set context_info @a", conn))
        {
            cmd.Parameters.Add("@user", SqlDbType.NVarChar, 64).Value = user;
            conn.Open();
            cmd.ExecuteNonQuery();
        }
        return conn;
    }
Run Code Online (Sandbox Code Playgroud)

您可以将其用作:

using(var conn = CreateConnection(connectionString, user))
{
   var cmd = new SqlCommand("select 1", conn);
   return conn.ExecuteScalar()
}
Run Code Online (Sandbox Code Playgroud)

对于 SqlConnection 的替代版本,您将需要重载 DbConnection 并实现 SqlConnection 的所有方法。执行方法将在下面添加查询并传入用户名作为额外参数。

declare @a varbinary(128) = convert(varbinary(128), @user + replicate(N' ', 64 - len(@user)))
set context_info @a 
Run Code Online (Sandbox Code Playgroud)

该类将用作:

using(var conn = new SqlContextInfoConnection(connectionString, user))
{
   var cmd = new SqlCommand("select 1", conn);
   conn.open;
   return conn.ExecuteScalar()
}
Run Code Online (Sandbox Code Playgroud)

我个人会实现选项 2,因为它更接近普通 SqlConnection 的工作方式。