Sql server SQL Server模拟和连接池

Sql server SQL Server模拟和连接池,sql-server,impersonation,Sql Server,Impersonation,我的任务是为一个遗留数据库编写一个web界面,在这个数据库中,所有用户都有数据库帐户并相应地分配了角色(当用户做某些事情时,我们到处都有触发器记录,所有这些都基于user\u name()) 为了使用任何远程现代化的工具,避免以明文形式存储用户密码,我正在连接一个对每个用户都具有模拟权限的应用程序级帐户,并尝试在运行任何SQL之前和之后运行executeas user=@username和Revert,以设置和重置执行上下文 不幸的是,连接池的reset\u connection调用正在破坏我的

我的任务是为一个遗留数据库编写一个web界面,在这个数据库中,所有用户都有数据库帐户并相应地分配了角色(当用户做某些事情时,我们到处都有触发器记录,所有这些都基于
user\u name()

为了使用任何远程现代化的工具,避免以明文形式存储用户密码,我正在连接一个对每个用户都具有模拟权限的应用程序级帐户,并尝试在运行任何SQL之前和之后运行
executeas user=@username
Revert
,以设置和重置执行上下文

不幸的是,连接池的reset\u connection调用正在破坏我的连接,它最终抛出了一些关于物理连接无效的严重错误

我可以通过不使用连接池来避免这个错误。但是,我的应用程序用户需要大量的特权才能真正执行模拟。此外,终止连接池是一件很糟糕的事情

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

实现一种“假”在应用程序/数据库代码没有重大更改的情况下,我建议使用委派将当前用户传输到数据库,并将对
user\u name()
的调用替换为对
dbo.fn\u user\u name()
的调用

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

创建fn\u user\u name()函数 我将创建一个函数fn_user_name,它将从连接的上下文信息()中提取用户名,或者在没有上下文信息时返回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
现在,您可以在代码中找到replace all calls to user_name(),并使用此函数替换它们

在.net中的db调用中嵌入上下文 这里有两个选项。或者创建自己的SqlConnection类,或者创建一个工厂方法,该方法将返回一个打开的SqlConnection,如下所示。factory方法有一个问题,即您运行的每个查询都是2个db调用。不过,这是最不需要编写的代码

    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;
    }
您可以将其用作:

using(var conn = CreateConnection(connectionString, user))
{
   var cmd = new SqlCommand("select 1", conn);
   return conn.ExecuteScalar()
}
对于SqlConnection的替代版本,您需要重载DbConnection并实现SqlConnection的所有方法。execute方法将在下面的查询前面加上前缀,并将用户名作为额外参数传入

declare @a varbinary(128) = convert(varbinary(128), @user + replicate(N' ', 64 - len(@user)))
set context_info @a 
然后,该类将用作:

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

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

我知道这是一个老问题,但是这篇文章是我找到的唯一有用的资源,我想我会分享我们基于答案的解决方案

我们还有一个使用
sp_setapprole
的遗留VB6应用程序(我知道这不太适合OPs的原始帖子)。我们共享同一数据库的.NET组件(本质上是应用程序框架的一部分)主要基于Linq到SQL

考虑到连接打开和关闭的次数,为datacontext连接设置approle被证明是很麻烦的

我们最终按照上面的建议编写了一个简单的包装器。唯一被重写的方法是
Open()
Close()
,这是设置和取消设置
approle
的地方

Public Class ManagedConnection
    Inherits Common.DbConnection

    Private mCookie As Byte()
    Private mcnConnection As SqlClient.SqlConnection

    Public Sub New()
        mcnConnection = New SqlClient.SqlConnection()
    End Sub

    Public Sub New(connectionString As String)
        mcnConnection = New SqlClient.SqlConnection(connectionString)
    End Sub

    Public Sub New(connectionString As String, credential As SqlClient.SqlCredential)
        mcnConnection = New SqlClient.SqlConnection(connectionString, credential)
    End Sub

    Public Overrides Property ConnectionString As String
        Get
            Return mcnConnection.ConnectionString
        End Get
        Set(value As String)
            mcnConnection.ConnectionString = value
        End Set
    End Property

    Public Overrides ReadOnly Property Database As String
        Get
            Return mcnConnection.Database
        End Get
    End Property

    Public Overrides ReadOnly Property DataSource As String
        Get
            Return mcnConnection.DataSource
        End Get
    End Property

    Public Overrides ReadOnly Property ServerVersion As String
        Get
            Return mcnConnection.ServerVersion
        End Get
    End Property

    Public Overrides ReadOnly Property State As ConnectionState
        Get
            Return mcnConnection.State
        End Get
    End Property

    Public Overrides Sub ChangeDatabase(databaseName As String)
        mcnConnection.ChangeDatabase(databaseName)
    End Sub

    Public Overrides Sub Close()

        Using cm As New SqlClient.SqlCommand("sp_unsetapprole")
            cm.Connection = mcnConnection
            cm.CommandType = CommandType.StoredProcedure
            cm.Parameters.Add("@cookie", SqlDbType.VarBinary, 8000).Value = mCookie

            cm.ExecuteNonQuery()
        End Using

        mcnConnection.Close()
    End Sub

    Public Overrides Sub Open()
        mcnConnection.Open()

        Using cm As New SqlClient.SqlCommand("sp_setapprole")
            cm.Connection = mcnConnection
            cm.CommandType = CommandType.StoredProcedure
            cm.Parameters.Add("@rolename", SqlDbType.NVarChar, 128).Value = "UID"
            cm.Parameters.Add("@password", SqlDbType.NVarChar, 128)Value = "PWD"
            cm.Parameters.Add("@fCreateCookie", SqlDbType.Bit).Value = True
            cm.Parameters.Add("@cookie", SqlDbType.VarBinary, 8000).Direction = ParameterDirection.InputOutput
            cm.ExecuteNonQuery()

            mCookie = cm.Parameters("@cookie").Value
        End Using
    End Sub

    Protected Overrides Function BeginDbTransaction(isolationLevel As IsolationLevel) As DbTransaction
        Return mcnConnection.BeginTransaction(isolationLevel)
    End Function

    Protected Overrides Function CreateDbCommand() As DbCommand
        Return mcnConnection.CreateCommand()
    End Function
End Class
之前:

Using dc As New SystemOptionDataContext(sConnectionString)
    intOption= dc.GetIntegerValue("SomeDatabaseOption")
End Using
之后:

Using dc As New SystemOptionDataContext(New ManagedConnection(strConnectionString))
    intOption= dc.GetIntegerValue("SomeDatabaseOption")
End Using

希望这对其他人有所帮助。

请注意,物理连接错误与以下错误一致:连接已断开,因为打开连接的主体随后采用了新的安全上下文,然后尝试在其模拟的安全上下文下重置连接。不支持此方案。请参阅联机丛书中的“模拟概述”。web应用程序用户是否使用windows身份验证进行连接,并且域是否支持kerberos?使用另一部分连接参数(例如应用程序名或工作站ID())以及
App\u Name()
Host\u Name()
函数如何?不理想,但考虑到限制,这可能是一个可接受的解决方案…@Filip no,username和password@gvee,我知道如何破坏连接池,这不是问题所在。此解决方案有几个问题:1。它与我们的VB6遗留应用程序不兼容,我无法更改,也无法使用上述代码。2.我不太喜欢每次执行某项操作时都执行两个单独的请求……您的客户机服务器应用程序不需要更改,如果未设置上下文,fn_user_name()将与user_name()相同。这就是为什么我建议选项2,其中上下文调用在要执行的sql字符串之前。今晚有空的时候,我会试着把课程安排好。伙计,我真的希望将来某一天我不必维护这个应用程序。