Как я могу надежно проверить личность клиента при выполнении вызовов DCOM на C # .NET 3.5 Server? - PullRequest
3 голосов
/ 20 апреля 2010

У меня есть старый Win32 C ++ DCOM Server, который я переписываю для использования C # .NET 3.5. Клиентские приложения находятся на удаленных компьютерах с Windows XP и также написаны на C ++. Эти клиенты должны оставаться неизменными, поэтому я должен реализовать интерфейсы для новых объектов .NET.

Это было сделано и успешно работает в отношении реализации интерфейсов, и все вызовы правильно выполняются от старых клиентов к новым объектам .NET.

Однако у меня проблемы с получением идентификатора вызывающего пользователя из клиента DCOM. Чтобы попытаться идентифицировать пользователя, который спровоцировал вызов DCOM, у меня есть следующий код на сервере ...

[DllImport("ole32.dll")]
static extern int CoImpersonateClient();

[DllImport("ole32.dll")]
static extern int CoRevertToSelf();

private string CallingUser
{
    get
    {
        string sCallingUser = null;

        if (CoImpersonateClient() == 0)
        {
            WindowsPrincipal wp = System.Threading.Thread.CurrentPrincipal as WindowsPrincipal;

            if (wp != null)
            {
                WindowsIdentity wi = wp.Identity as WindowsIdentity;

                if (wi != null && !string.IsNullOrEmpty(wi.Name))
                    sCallingUser = wi.Name;
            }

            if (CoRevertToSelf() != 0)
                ReportWin32Error("CoRevertToSelf");
        }
        else
            ReportWin32Error("CoImpersonateClient");

        return sCallingUser;
    }
}

private static void ReportWin32Error(string sFailingCall)
{
    Win32Exception ex = new Win32Exception();
    Logger.Write("Call to " + sFailingCall + " FAILED: " + ex.Message);
}

Когда я получаю свойство CallingUser, значение, возвращаемое первые несколько раз, является правильным, и правильное имя пользователя идентифицируется, однако после того, как 3 или 4 разных пользователя успешно сделали вызовы (и это меняется, поэтому я могу ' (если быть более точным), дальнейшие пользователи, по-видимому, идентифицируются как пользователи, которые делали более ранние вызовы.

Что я заметил, так это то, что первые несколько пользователей обрабатывают свои вызовы DCOM в своем собственном потоке (то есть все вызовы определенного клиента обрабатываются одним уникальным потоком), а затем последующие пользователи обрабатываются те же потоки, что и у предыдущих пользователей, и после вызова CoImpersonateClient(), CurrentPrincipal совпадает с исходным пользователем этого потока.

Для иллюстрации:

Пользователь Том делает вызовы DCOM, которые обрабатываются потоком 1 (CurrentPrincipal правильно идентифицирует Тома)

Пользователь Дик делает вызовы DCOM, которые обрабатываются потоком 2 (CurrentPrincipal правильно идентифицирует Дика)

Пользователь Harry делает вызовы DCOM, которые обрабатываются потоком 3 (CurrentPrincipal правильно идентифицирует Гарри)

Пользователь Боб делает вызовы DCOM, которые обрабатываются потоком 3 (CurrentPrincipal неправильно идентифицирует его как Гарри)

Как вы можете видеть на этом рисунке, вызовы клиентов Гарри и Боба обрабатываются в потоке 3, и сервер идентифицирует вызывающего клиента как Гарри.

Есть ли что-то, что я делаю не так? Существуют ли какие-либо предостережения или ограничения на использование Олицетворения таким образом? Есть ли лучший или иной способ, которым я могу НАДЕЖНО добиться того, что я пытаюсь сделать?

Любая помощь будет принята с благодарностью.

1 Ответ

1 голос
/ 22 апреля 2010

ОК, поэтому я выбрал другой подход и, наконец, придумаю метод, который, кажется, работает (протестирован для 8 различных удаленных пользователей).

Я отказался от маршрута подражания в пользу ClientBlankets ...

[DllImport("ole32.dll")]
static extern int CoQueryClientBlanket(out IntPtr pAuthnSvc, out IntPtr pAuthzSvc,
    [MarshalAs(UnmanagedType.LPWStr)] out StringBuilder pServerPrincName, out IntPtr
    pAuthnLevel, out IntPtr pImpLevel, out IntPtr pPrivs, out IntPtr pCapabilities);

public static string CallingUser
{
    get
    {
        IntPtr pAthnSvc = new IntPtr();
        IntPtr pAthzSvc = new IntPtr();
        StringBuilder pServerPrincName = new StringBuilder();
        IntPtr pAuthnLevel = new IntPtr();
        IntPtr pImpLevel = new IntPtr();
        IntPtr pPrivs = new IntPtr();
        IntPtr pCaps = new IntPtr(4);
        string sCallingUser = string.Empty;

        try
        {
            CoQueryClientBlanket(out pAthnSvc,
                out pAthzSvc,
                out pServerPrincName,
                out pAuthnLevel,
                out pImpLevel,
                out pPrivs,
                out pCaps);
        }
        catch (Exception ex)
        {
            Logger.Write(ex.Message);
        }
        finally
        {
            sCallingUser = System.Runtime.InteropServices.Marshal.PtrToStringAuto(pPrivs);
        }

        return sCallingUser;
    }
}

Использование CoCreateClientBlanket, похоже, дает желаемые результаты, и я могу надежно получать личность вызывающего пользователя каждый раз, независимо от того, какой поток используется для обработки сообщения.

...