NTLM аутентификация.Не могу заставить его работать в IHttpModule.AcceptSecurityContext всегда терпит неудачу - PullRequest
2 голосов
/ 22 июня 2011

Вот настройки.На сайте ASP.Net мы хотим иметь аутентификацию NTLM на определенных страницах.То, как это будет работать, - это модуль, который будет отвечать только на эти страницы, а затем выполнять обратный запрос / ответ, требуемый для аутентификации NTLM.

NTLM не так уж и прост, поэтому после некоторого копания я обнаружил, что в Cassini встроена эта функциональность:

http://cassinidev.codeplex.com/SourceControl/changeset/view/70631#1365123

Вот соответствующий метод:

    public unsafe bool Authenticate(string blobString)
    {
        _blob = null;
        byte[] buffer = Convert.FromBase64String(blobString);
        byte[] inArray = new byte[0x4000];
        fixed (void* ptrRef = &_securityContext)
        {
            fixed (void* ptrRef2 = &_inputBuffer)
            {
                fixed (void* ptrRef3 = &_outputBuffer)
                {
                    fixed (void* ptrRef4 = buffer)
                    {
                        fixed (void* ptrRef5 = inArray)
                        {
                            IntPtr zero = IntPtr.Zero;
                            if (_securityContextAcquired)
                            {
                                zero = (IntPtr) ptrRef;
                            }
                            _inputBufferDesc.ulVersion = 0;
                            _inputBufferDesc.cBuffers = 1;
                            _inputBufferDesc.pBuffers = (IntPtr) ptrRef2;
                            _inputBuffer.cbBuffer = (uint) buffer.Length;
                            _inputBuffer.BufferType = 2;
                            _inputBuffer.pvBuffer = (IntPtr) ptrRef4;
                            _outputBufferDesc.ulVersion = 0;
                            _outputBufferDesc.cBuffers = 1;
                            _outputBufferDesc.pBuffers = (IntPtr) ptrRef3;
                            _outputBuffer.cbBuffer = (uint) inArray.Length;
                            _outputBuffer.BufferType = 2;
                            _outputBuffer.pvBuffer = (IntPtr) ptrRef5;
                            int num = Interop.AcceptSecurityContext(ref _credentialsHandle, zero,
                                                                    ref _inputBufferDesc, 20,
                                                                    0, ref _securityContext, ref _outputBufferDesc,
                                                                    ref _securityContextAttributes, ref _timestamp);
                            if (num == 0x90312)
                            {
                                _securityContextAcquired = true;
                                _blob = Convert.ToBase64String(inArray, 0, (int) _outputBuffer.cbBuffer);
                            }
                            else
                            {
                                if (num != 0)
                                {
                                    return false;
                                }
                                IntPtr phToken = IntPtr.Zero;
                                if (Interop.QuerySecurityContextToken(ref _securityContext, ref phToken) != 0)
                                {
                                    return false;
                                }
                                try
                                {
                                    using (WindowsIdentity identity = new WindowsIdentity(phToken))
                                    {
                                        _sid = identity.User;
                                    }
                                }
                                finally
                                {
                                    Interop.CloseHandle(phToken);
                                }
                                _completed = true;
                            }
                        }
                    }
                }
            }
        }
        return true;
    }

Вот как Cassini использует этот код:

http://cassinidev.codeplex.com/SourceControl/changeset/view/70631#1365119

    private bool TryNtlmAuthenticate()
    {
        try
        {
            using (var auth = new NtlmAuth())
            {
                do
                {
                    string blobString = null;
                    string extraHeaders = _knownRequestHeaders[0x18];
                    if ((extraHeaders != null) && extraHeaders.StartsWith("NTLM ", StringComparison.Ordinal))
                    {
                        blobString = extraHeaders.Substring(5);
                    }
                    if (blobString != null)
                    {
                        if (!auth.Authenticate(blobString))
                        {
                            _connection.WriteErrorAndClose(0x193);
                            return false;
                        }
                        if (auth.Completed)
                        {
                            goto Label_009A;
                        }
                        extraHeaders = "WWW-Authenticate: NTLM " + auth.Blob + "\r\n";
                    }
                    else
                    {
                        extraHeaders = "WWW-Authenticate: NTLM\r\n";
                    }
                    SkipAllPostedContent();
                    _connection.WriteErrorWithExtraHeadersAndKeepAlive(0x191, extraHeaders);
                } while (TryParseRequest());
                return false;
            Label_009A:
                if (_host.GetProcessSid() != auth.SID)
                {
                    _connection.WriteErrorAndClose(0x193);
                    return false;
                }
            }
        }
        catch
        {
            try
            {
                _connection.WriteErrorAndClose(500);
            }
            // ReSharper disable EmptyGeneralCatchClause
            catch
            // ReSharper restore EmptyGeneralCatchClause
            {
            }
            return false;
        }
        return true;
    }

Вот основной рабочий процесс.В первый раз он просто добавляет «WWW-Authenticate: NTLM» в заголовок.Клиент отвечает NTLM: некоторая строка токена .В этот момент Кассини берет эту строку и использует ее для вызова основного вызова WinAPI AcceptSecurityContext.Это генерирует другую строку токена, которая в свою очередь отправляется обратно клиенту.Затем клиент отправляет обратно еще одну зашифрованную строку токена, а Кассини снова передает ее методу AcceptSecurityContext.На этом этапе в приложении Cassini аутентификация проходит успешно, и мы все в порядке.

Я попытался воспроизвести это в своем модуле, но по какой-то причине при заключительном рукопожатии мне не удалось пройти аутентификацию:

public class TestModule : IHttpModule
{
    public void Dispose()
    {
    }

    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
    }

    void context_BeginRequest(object sender, EventArgs e)
    {
        var context = HttpContext.Current;
        var headers = context.Request.Headers;
        if (String.IsNullOrEmpty(headers.Get("Authorization")))
        {
            context.Response.StatusCode = 401;
            context.Response.AddHeader("WWW-Authenticate", "NTLM");
        }
        else
        {
            Step2(context);
        }


    }

    private void Step2(HttpContext httpContext)
    {
        using (var auth = new NtlmAuth())
        {
            var header = httpContext.Request.Headers["Authorization"].Substring(5);
            var result = auth.Authenticate(header); //third time around, this returns false. AcceptSecurityContext in NtmlAuth fails....
            if (!result)
            {
                ReturnUnauthorized(httpContext);
            }
            else if (!auth.Completed)
            {
                HttpContext.Current.Response.Charset = null;
                HttpContext.Current.Response.ContentType = null;
                httpContext.Response.StatusCode = 401;
                httpContext.Response.AddHeader("WWW-Authenticate", "NTLM " + auth.Blob);
                httpContext.Response.End();
            }
            else
            {
                httpContext.Response.StatusCode = 200;
                httpContext.Response.Write("Yay!");
                httpContext.Response.End();
            }
        }
    }

    private void ReturnUnauthorized(HttpContext httpContext)
    {
        httpContext.Response.StatusCode = 403;
        httpContext.Response.End();
    }
}

Каждый раз, когда я вызываю его, я получаю ответ: «SEC_E_INVALID_TOKEN», что в соответствии с документацией означает: «Сбой функции. Токен, переданный функции, не являетсядействует. ".Мой тестовый сайт работает в IIS, и этот модуль работает для всех запросов на этом этапе.У меня установлен Keep-Alive в заголовках (NTLM требует одно и то же соединение во время последних двух ответов / запросов).

Другие вещи, которые я пробовал: используя Fiddler, я посмотрел на заголовки, отправляемые обратно из Cassini, и попытался, чтобы мой модуль отправил те же самые заголовки обратно.Неудачно.Я пытался изменить пользователя, под которым работает сайт, но это тоже не помогло.

По сути, мой вопрос: почему он продолжает терпеть неудачу?Почему Кассини может успешно пройти аутентификацию, а мой веб-сайт - нет?

Ответы [ 2 ]

2 голосов
/ 02 мая 2014

Я тоже столкнулся с этой проблемой. Когда вы просматриваете документацию и код Authenticate метода, который использует Cassini, вы видите, что он ожидает, что состояние класса NtlmAuth будет одинаковым для запросов шага 2 и шага 3.

Из документации для параметра phContext (2nd): при первом вызове AcceptSecurityContext (NTLM) этот указатель имеет значение NULL. При последующих вызовах phContext - это дескриптор частично сформированного контекста, который был возвращен в параметре phNewContex t при первом вызове.

Из кода: когда первый вызов AcceptSecurityContext завершается успешно, он устанавливает логическую переменную _securityContextAcquired в true, он получает дескриптор securitycontext ( _securityContext ) и создает большой двоичный объект, который вы нужно отправить обратно в ответ.

Вы имели это право. Но поскольку вы создаете экземпляр NtlmAuth при каждом запросе, вы теряете свое состояние, следовательно, _securityContextAcquired имеет значение false, _securityContext является нулевым для вашего запроса шага 3, он передает нулевой параметр как 2-й параметр в AcceptSecurityContext, и вы никогда не проходите аутентификацию. Поэтому вам нужно найти способ кэшировать состояние класса или хотя бы кэшировать securityContext, полученный в запросе шага 2 (и, конечно, сайт должен работать с полным доверием).

0 голосов
/ 30 июня 2011

Я думаю, что это связано с разрешениями на уровне ОС. Asp.net обычно выполняется как NetworkService, но может выполнять неуправляемые вызовы как Inet_machine, у которого нет разрешения на использование вызовов API.

Cassini работает под вашей учетной записью компьютера, поэтому выполняет вызовы по-другому.

Вы можете попробовать использовать директиву олицетворения конфигурации или изменить пользователя, от имени которого выполняется пул приложений (зависит от вашего IIS).

Еще одна мысль: рассматривали ли вы возможность использования IIS для блокировки доступа к файлам с ограниченным доступом вместо того, чтобы делать это в asp.net?

...