【问题标题】:NTLM Authentication. Can't get it to work in an IHttpModule. AcceptSecurityContext always failsNTLM 身份验证。无法让它在 IHttpModule 中工作。 AcceptSecurityContext 总是失败
【发布时间】:2011-06-22 18:31:23
【问题描述】:

这是设置。在 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 响应:一些令牌字符串。此时,Cassini 获取此字符串,并使用它来调用底层的 AcceptSecurityContext WinAPI 调用。这会生成另一个令牌字符串,然后将其发送回客户端。然后客户端发回另一个加密的令牌字符串,Cassini 将其再次传递给 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”,根据documentation 的意思是:“函数失败。传递给函数的令牌无效。”。我的测试站点在 IIS 中运行,此时此模块针对所有请求运行。我在标头中设置了 Keep-Alive(NTLM 在最后两个响应/请求期间需要相同的连接)。

我尝试过的其他事情:使用 Fiddler,我查看了从 Cassini 发回的标头,并尝试让我的模块发回相同的标头。没运气。我尝试更改站点运行的用户,但这也无济于事。

基本上,我的问题是,为什么它总是失败?为什么 Cassini 可以成功验证,而我的网站却不能?

【问题讨论】:

  • 不确定这是否与此有关,但您的网站在 IIS 中运行的信任级别是多少?
  • @rsbarro:是的,我也试过了,网站在完全信任下运行。
  • 你有没有找到解决这个问题的方法?
  • @chrisortman 遗憾的是,没有....尝试了一段时间后,我最终放弃了这种方法。已经一年半多了,所以我不记得我们最终做了什么,但我认为我们最终采用了标准形式的身份验证。对不起!
  • 无赖,我设法使用反射来获取 HttpContext.WorkerRequest.GetUserToken() 但对我的灵魂的伤害很高。

标签: c# asp.net iis ntlm ihttpmodule


【解决方案1】:

我也遇到了这个问题。当您查看 documentation 和 Cassini 使用的 Authenticate 方法的代码时,您会看到它期望 NtlmAuth 类的状态对于第 2 步和第 3 步请求是相同的。

来自 phContext (2nd) 参数的文档:在第一次调用 AcceptSecurityContext (NTLM) 时,此指针为 NULL。在随后的调用中,phContext 是第一次调用在phNewContext 参数中返回的部分形成的上下文的句柄。

从代码中:当第一次调用AcceptSecurityContext 成功时,它将布尔变量_securityContextAcquired 设置为true,它获取securitycontext 的句柄(_securityContext)并创建一个blob您需要回复您的回复。

你有这个权利。但是,由于您在每个请求上实例化 NtlmAuth 都会丢失您的状态,因此 _securityContextAcquired 为 false,_securityContext 对于您的第 3 步请求为 null,它将 null 作为第二个参数传递给 AcceptSecurityContext 并且您永远不会获得身份验证。所以你需要找到一种方法来缓存类的状态,或者至少缓存在第 2 步请求中获得的securityContext(当然站点需要在完全信任下运行)。

【讨论】:

    【解决方案2】:

    我认为这与操作系统级别的权限有关。 Asp.net 通常作为 NetworkService 执行,但可能会将非托管调用作为 Inet_machine,它没有使用 API 调用的权限。

    Cassini 在您的计算机帐户下运行,因此执行调用的方式不同。

    您可以尝试使用模拟配置指令或更改应用程序池执行的用户(取决于您的 IIS)。

    另一个想法,您是否考虑过使用 IIS 来阻止对受限文件的访问,而不是在 asp.net 中这样做?

    【讨论】:

      猜你喜欢
      • 2011-05-19
      • 1970-01-01
      • 1970-01-01
      • 2015-01-10
      • 2013-01-02
      • 1970-01-01
      • 2016-06-23
      • 2016-02-13
      • 1970-01-01
      相关资源
      最近更新 更多