SQL Server模拟和连接池

时间:2014-01-30 18:28:42

标签: sql-server impersonation

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

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

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

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

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

2 个答案:

答案 0 :(得分: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

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

在.net

中的db调用中嵌入上下文

这里有2个选项。或者您创建自己的SqlConnection类,或者创建一个工厂方法,它将返回一个打开的sqlconnection,如下所示。工厂方法存在的问题是,您运行的每个查询都将是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的工作方式。

答案 1 :(得分:0)

我知道这已经过时了,不过这篇文章是我找到的唯一有用的资源,我想我会分享我的解决方案,它建立在Filip De Vos回答的基础上。

我们还有一个利用sp_setapprole的传统VB6应用程序(我很欣赏这不太符合OP的原始帖子)。我们共享同一数据库的.NET组件(实际上是应用程序框架的一部分)主要基于Linq to SQL。

考虑到打开和关闭连接的次数,设置datacontext连接的批准证明很麻烦。

我们最终使用如上所述编写一个简单的包装器。唯一被覆盖的方法是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

希望这有助于其他人。