NTLM身份验证.可以';无法使其在IHttpModule中工作.AcceptSecurityContext总是失
本文关键字:IHttpModule 工作 AcceptSecurityContext 可以 身份验证 NTLM | 更新日期: 2023-09-27 18:00:20
以下是设置。在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;
}
以下是卡西尼号如何使用该代码:
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-Authenticae: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",根据文档,这意味着:"函数失败。传递给函数的令牌无效。"。我的测试站点正在IIS中运行,此时该模块会为所有请求运行。我在标头中设置了Keep Alive(NTLM在最后两个响应/请求期间需要相同的连接)。
我尝试过的其他事情:使用Fiddler,我查看了从卡西尼发回的标题,并尝试让我的模块发回相同的标题。运气不好。我试过更改网站运行的用户,但这也没有帮助。
基本上,我的问题是,为什么它一直失败?为什么卡西尼号可以成功认证,而我的网站却不能?
我也遇到了这个问题。当您查看文档和Cassini使用的Authenticate
方法的代码时,您会发现它希望步骤2和步骤3请求的NtlmAuth
类的状态相同。
根据phContext(第二个)参数的文档:在第一次调用AcceptSecurityContext
(NTLM)时,此指针为NULL。在随后的调用中,phContext
是第一次调用在phNewContex
t参数中返回的部分形成的上下文的句柄。
从代码中可以看出:当对AcceptSecurityContext
的第一次调用成功时,它将布尔变量_securityContextAcquired
设置为true,并获得securitycontext
的句柄(_securityContext),并创建一个需要在响应中发回的blob。
你有这个权利。但是,由于您在每个丢失状态的请求上实例化NtlmAuth
,因此_securityContextAcquired
为false,_securityContext
对于步骤3请求为null,它将null作为第二个参数传递给AcceptSecurityContext
,您永远不会获得身份验证。因此,您需要找到一种方法来缓存类的状态,或者至少缓存在步骤2请求中获得的securityContext
(当然,站点需要在完全信任的情况下运行)。
我认为这与操作系统级别的权限有关。Asp.net通常作为NetworkService执行,但可能会将非托管调用作为Inet_machine进行,Inet_machine无权使用API调用。
Cassini在你的机器帐户下运行,所以执行调用的方式不同。
您可以尝试使用模拟配置指令或更改应用程序池执行的用户(取决于您的IIS)。
另一个想法是,您是否考虑过使用IIS来阻止对受限文件的访问,而不是在asp.net中这样做?