C# NTLM身份验证。Can';我不能让它在IHTTP模块中工作。AcceptSecurityContext始终失败

C# NTLM身份验证。Can';我不能让它在IHTTP模块中工作。AcceptSecurityContext始终失败,c#,asp.net,iis,ntlm,ihttpmodule,C#,Asp.net,Iis,Ntlm,Ihttpmodule,这是设置。在ASP.Net站点上,我们希望在特定页面上进行NTLM身份验证。其工作方式是,将有一个模块只响应这些页面,然后执行NTLM身份验证所需的来回请求/响应 NTLM并不是那么容易,所以经过一些挖掘,我发现卡西尼号实际上内置了以下功能: 以下是相关的方法: public unsafe bool Authenticate(string blobString) { _blob = null; byte[] buffer = Convert.Fr

这是设置。在ASP.Net站点上,我们希望在特定页面上进行NTLM身份验证。其工作方式是,将有一个模块只响应这些页面,然后执行NTLM身份验证所需的来回请求/响应

NTLM并不是那么容易,所以经过一些挖掘,我发现卡西尼号实际上内置了以下功能:

以下是相关的方法:

    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;
    }
以下是卡西尼号如何使用该代码:

这是基本的工作流程。第一次,它只是将“WWW-Authenticate:NTLM”添加到头中。客户端使用NTLM:some令牌字符串进行响应。此时,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();
    }
}
每次调用它时,我都会得到一个响应:“secu_E_INVALID_TOKEN”,它的意思是:“函数失败。传递给函数的令牌无效。”。我的测试站点正在IIS中运行,此模块在此时针对所有请求运行。我在头文件中设置了Keep Alive(NTLM在最后两个响应/请求期间需要相同的连接)

我尝试过的其他事情:使用Fiddler,我查看了从卡西尼号发回的头文件,并尝试让我的模块发回同样的头文件。不走运。我尝试过改变网站运行的用户,但也没有帮助


基本上,我的问题是,为什么它总是失败?为什么卡西尼能成功地进行身份验证,而我的网站却不能

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

Cassini在你的机器帐户下运行,因此执行调用的方式也不同

您可以尝试使用impersonate config指令或更改应用程序池执行为的用户(取决于您的IIS)


另一个想法是,您是否考虑过使用IIS来阻止对受限制文件的访问,而不是在asp.net中执行此操作?

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

从phContext(第2个)参数的文档中:在第一次调用
AcceptSecurityContext
(NTLM)时,此指针为空。在后续调用中,
phContext
是第一次调用在
phNewContex
t参数中返回的部分形成的上下文的句柄

从代码:当对
AcceptSecurityContext
的第一次调用成功时,它将布尔变量
\u securityContextAcquired
设置为true,它获得
securitycontext
(\u securitycontext)的句柄,并创建需要在响应中发回的blob


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

不确定这是否与此有关,但您的站点在IIS中运行的信任级别是多少?@rsbarro:是的,我也尝试过,该网站是在完全信任的情况下运行的。你有没有找到解决这个问题的方法?@chrisortman遗憾地说,没有。。。。在尝试了一段时间后,我最终放弃了这种方法。已经过去一年半了,所以我不记得我们最终做了什么,但我想我们最终还是求助于标准表单auth。很抱歉糟糕的是,我设法使用反射来获取HttpContext.WorkerRequest.GetUserToken(),但对我灵魂的伤害很大。
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();
    }
}