Олицетворение и объединение SQL Server
Мне задали задачу написать веб-интерфейс для устаревшей базы данных, где у всех пользователей есть учетные записи баз данных и соответственно назначены роли (у нас есть триггеры по всему месту записи, когда пользователи делают определенные вещи, все на основе user_name()
).
Чтобы использовать что-нибудь удаленно современное и чтобы не хранить пароль пользователя в текстовом формате, я подключаюсь к учетной записи уровня App, у которой есть привилегии олицетворения для каждого пользователя, и я пытаюсь запустить Execute As [email protected]
и Revert
установить и reset контекст выполнения до и после запуска любого SQL.
К сожалению, вызов пула соединений reset_connection
отключает w/my Connection, и он задерживает выбросы некоторых неприятных ошибок в отношении физического подключения, которое недействительно...
Я могу обойти эту ошибку, не используя пул соединений. Но тогда мой пользователь приложения нуждается в безумном количестве привилегий, чтобы фактически выполнить олицетворение. Кроме того, уничтожение пула соединений является обломком...
Как я могу сделать это без ущерба для безопасности или производительности? Имейте в виду, что я не могу изменить тот факт, что у моих пользователей есть логины базы данных, и я действительно не в восторге от того, что пользовательские пароли восстанавливаются. Является ли мой единственный вариант обхода пула соединений, чтобы я мог выдавать себя (и использовать пользователя sa, чтобы у меня было достаточно прав для фактического олицетворения кого-то)?
Ответы
Ответ 1
Чтобы реализовать своего рода "фальшивую" делегацию без огромных изменений в коде приложения/базы данных, я предлагаю использовать context_info() для переноса текущего пользователя в базу данных и замените вызовы на user_name()
на вызовы dbo.fn_user_name()
.
Пример создания этого решения
Создать функцию fn_user_name()
Я бы создал функцию fn_user_name, которая будет извлекать имя пользователя из контекста_info() в соединении или возвращать имя_пользователя(), когда нет доступной информации контекста. обратите внимание, что контекст соединения представляет собой 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() в своем коде и заменить их на эту функцию.
Вставить контекст в свои вызовы db в .net
Здесь есть 2 варианта. Или вы создаете свой собственный класс SqlConnection или создаете метод factory, который возвращает открытое соединение sql, как показано ниже. Метод factory имеет такую проблему, что каждый запрошенный вами запрос будет иметь 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;
}
вы использовали бы это как:
using(var conn = CreateConnection(connectionString, user))
{
var cmd = new SqlCommand("select 1", conn);
return conn.ExecuteScalar()
}
Для альтернативной версии SqlConnection вам нужно будет перегрузить DbConnection и реализовать все методы SqlConnection. Методы выполнения предваряют запрос ниже и передают имя пользователя в качестве дополнительного параметра.
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.
Ответ 2
Я знаю, что это старо, однако этот пост является единственным полезным ресурсом, который я нашел, и я решил поделиться своим решением, которое строится на Filip De Vos ответ.
У нас также есть устаревшее приложение VB6, в котором используется sp_setapprole
(я ценю, что это не совсем соответствует оригинальной записи OPs). Наши компоненты .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
Надеюсь, что это поможет другим.