我已经完成了为遗留数据库编写Web界面的任务,我们拥有所有用户都拥有数据库帐户并相应地分配角色(当用户执行某些操作时,我们会在整个地方记录触发器,所有这些都基于user_name()
)。
为了使用任何远程现代的东西,并避免以纯文本形式存储用户的密码我正在连接具有每个用户的模拟权限的应用级帐户,并且我正在尝试运行{{1} }和Execute As User=@username
在运行任何SQL之前和之后设置和重置执行上下文。
不幸的是,连接池的Revert
调用正在与我的连接混乱,并且它最终会抛出一些关于物理连接无效的令人讨厌的错误...
我可以通过不使用连接池来解决此错误。但是,我的应用程序用户需要疯狂的权限来实际执行模拟。此外,杀死连接池是一个无赖......
如何在不牺牲安全性或性能的情况下实现这一目标?请记住,我无法改变我的用户拥有数据库登录的事实,而且我对以可检索的方式存储用户密码感到非常兴奋。我是绕过连接池的唯一选择,因此我可以冒充(并使用sa用户,因此我有足够的权限来实际冒充某人)?
答案 0 :(得分:2)
实施一种"假的"在没有对应用程序/数据库代码进行大量更改的情况下,我建议使用context_info()将当前用户传输到数据库,并通过调用user_name()
来替换对dbo.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()的调用,并将其替换为此函数。
这里有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
希望这有助于其他人。