2

これがセットアップです。ASP.Netサイトでは、特定のページでNTLM認証を使用する必要があります。これが機能する方法は、それらのページにのみ応答し、NTLM認証に必要な要求/応答を行ったり来たりするモジュールが存在することです。

NTLMはそれほど簡単ではないので、少し掘り下げてみると、カッシーニには実際にこの機能が組み込まれていることがわかりました。

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;
    }

カッシーニがそのコードを使用する方法は次のとおりです。

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で応答します:いくつかのトークン文字列。この時点で、Cassiniはこの文字列を受け取り、それを使用して基になるAcceptSecurityContextWinAPI呼び出しを呼び出します。これにより、別のトークン文字列が生成され、クライアントに返送されます。次に、クライアントは別の暗号化されたトークン文字列を送り返し、CassiniはそれをAcceptSecurityContextメソッドに再度渡します。カッシーニアプリのこの時点で、認証は成功し、私たちは皆元気です。

モジュールでこれを再現しようとしましたが、何らかの理由で、最後のハンドシェイクで認証に失敗しました。

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は、最後の2つの応答/要求中に同じ接続を必要とします)。

私が試した他のこと:Fiddlerを使用して、カッシーニから返送されているヘッダーを確認し、モジュールに同じヘッダーを返送させてみました。運がない。サイトを運営しているユーザーを変更しようとしましたが、それも役に立ちませんでした。

基本的に、私の質問は、なぜそれが失敗し続けるのかということです。カッシーニが正常に認証できるのに、私のWebサイトが認証できないのはなぜですか?

4

2 に答える 2

2

私もこの問題に遭遇しました。ドキュメントと Cassini が使用するメソッドのコードを確認すると、ステップ 2 とステップ 3 のリクエストでクラスAuthenticateの状態が同じであることが期待されていることがわかります。NtlmAuth

phContext (2 番目) パラメーターのドキュメントから: (NTLM) への最初の呼び出しでAcceptSecurityContextは、このポインターは NULL です。後続の呼び出しでは、最初の呼び出しによって t パラメーターphContextで返された、部分的に形成されたコンテキストへのハンドルです。phNewContex

コードから: への最初の呼び出しがAcceptSecurityContext成功すると、ブール変数_securityContextAcquiredが true に設定され、 securitycontext( _securityContext ) へのハンドルが取得され、応答で返送する必要がある blob が作成されます。

あなたにはその権利がありました。ただし、NtlmAuth状態を失うすべてのリクエストでインスタンス化するため、ステップ 3 のリクエストで_securityContextAcquiredは false であり_securityContext、2 番目のパラメータとして null が渡され、AcceptSecurityContext認証されることはありません。したがって、クラスの状態をキャッシュする方法を見つけるか、少なくともsecurityContextステップ 2 の要求で取得したものをキャッシュする方法を見つける必要があります (もちろん、サイトは完全な信頼の下で実行する必要があります)。

于 2014-05-02T08:46:22.297 に答える
0

OSレベルの権限に関係していると思います。Asp.net は通常、NetworkService として実行されますが、API 呼び出しを使用する権限がない Inet_machine としてアンマネージ呼び出しを行っている可能性があります。

Cassini はマシン アカウントで実行されるため、呼び出しの実行方法が異なります。

impersonate config ディレクティブを使用するか、アプリケーション プールを実行するユーザーを変更することができます (IIS によって異なります)。

別の考えとして、asp.net ではなく、IIS を使用して制限付きファイルへのアクセスをブロックすることを検討しましたか?

于 2011-06-30T15:46:17.173 に答える