我正在开发一个ASP.NET(C#)应用程序,该应用程序基本上是用于运行常规管理任务的Powershell脚本的网关。其中一些脚本使用ActiveDirectory RSAT模块,我发现其中一些cmdlet在通过网关调用时无法正常运行,并且跟踪似乎暗示与域控制器的连接成功,但随后被关闭由DC。
以下代码是一个ASP.NET Web表单,其中包含一个用于指定用户名的文本输入。基本上,它执行以下操作:
通过将用户名称读取到表单上的输出元素来确认成功。
protected void LookupButton_Click( object sender, EventArgs e ) {
WindowsImpersonationContext impersonationContext = ((WindowsIdentity)User.Identity).Impersonate();
Runspace runspace;
Pipeline pipe;
try {
runspace = new_runspace();
runspace.Open();
pipe = runspace.CreatePipeline();
Command cmd = new Command("Get-ADUser");
cmd.Parameters.Add(new CommandParameter("Identity", text_username.Text));
pipe.Commands.Add(cmd);
PSObject ps_out = pipe.Invoke().First();
output.Text = ps_out.Properties["Name"].Value.ToString();
}
catch( Exception ex ) {
error.Text = ex.ToString();
}
finally {
impersonationContext.Undo();
}
}
private Runspace new_runspace( ) {
InitialSessionState init_state = InitialSessionState.CreateDefault();
init_state.ThreadOptions = PSThreadOptions.UseCurrentThread;
init_state.ImportPSModule(new[] { "ActiveDirectory" });
return RunspaceFactory.CreateRunspace(init_state);
}
有趣的部分是catch块(强调我的)中暴露的错误消息中的特定措辞:
System.Management.Automation.CmdletInvocationException:无法执行 联系服务器。这可能是因为该服务器不存在,它 目前已关闭,或者它没有Active Directory Web 服务正在运行---> Microsoft.ActiveDirectory.Management.ADServerDownException:无法执行 联系服务器。这可能是因为该服务器不存在,它 目前已关闭,或者它没有Active Directory Web 服务正在运行---> System.ServiceModel.CommunicationException:The 套接字连接中止。这可能是由错误引起的 处理您的消息或超过接收超时 远程主机或底层网络资源问题。本地套接字 超时为'00:01:59.6870000'。 ---> System.IO.IOException:读取 操作失败,请参阅内部异常。 ---> System.ServiceModel.CommunicationException:套接字连接是 中止。这可能是由处理您的消息或错误导致的 接收远程主机或基础主机超时超时 网络资源问题。本地套接字超时为'00:01:59.6870000'。 ---> System.Net.Sockets.SocketException:远程主机强行关闭现有连接
上层异常表明存在超时,但较低的异常没有指示超时(并且从命令返回只需要几秒钟)。在无法访问服务器的情况下,最低级别的异常消息说明了这一点,但是这个特定的措辞让我觉得在这里有一些身份验证(或其他安全)问题。
更新2013年2月19日: 使用描述here的模拟方法时,脚本按预期运行。这让我觉得问题可能是Windows身份验证提供的WindowsIdentity对象可能不适用于基本上对AD进行RPC调用的脚本。不幸的是,我不想放弃使用Windows身份验证,因为我必须在我的应用程序代码中处理用户的密码(这不是我想要的责任)。
我无法找到任何关于Windows auth正在做什么的文档,或者使用它会导致什么样的模仿。是否可以在使用Windows身份验证时执行此操作,或者我将不得不要求用户向我提供密码?
答案 0 :(得分:4)
此问题的根本原因是,在IIS中使用Windows身份验证时,安全令牌仅对向Web服务器计算机验证Web客户端计算机有效。相同的令牌对于将Web服务器计算机验证到任何其他计算机无效,这就是我的应用程序尝试执行的操作:
将这称为Kerberos的“副作用”并不完全正确,但事后我并不明白,尽管事后看来这很明显。我希望有人可以从这些信息中受益。
解决方案是让应用程序生成自己的安全令牌,然后通过对LogonUser()进行API调用,将其用作Web用户身份验证其他计算机上的服务。您的应用程序代码需要访问用户的清除密码,这可以通过在IIS中仅启用HTTP基本身份验证来实现。 Web服务器仍将强制执行相同的身份验证和授权规则,但用户名和密码都将可用于您的应用程序代码。请注意,这些凭据会以明文的形式传输到网络服务器,因此在生产中使用此凭据之前,您需要使用SSL。
我根据描述here的过程创建了一个小助手类,以促进此过程。这是一个简短的演示:
登录助手:
public class IdentityHelper {
[DllImport("advapi32.dll")]
private static extern int LogonUserA( String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken );
[DllImport("advapi32.dll",
CharSet = CharSet.Auto,
SetLastError = true)]
private static extern int DuplicateToken( IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken );
[DllImport("kernel32.dll",
CharSet = CharSet.Auto)]
private static extern bool CloseHandle( IntPtr handle );
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;
public const int IMPERSONATION_LEVEL_IMPERSONATE = 2;
public static WindowsIdentity Logon( string username, string password, string domain = "" ) {
IntPtr token = IntPtr.Zero;
WindowsIdentity wid = null;
if( domain == "" ) {
split_username(username, ref username, ref domain);
}
if( LogonUserA(username, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0 ) {
wid = WIDFromToken(token);
}
if( token != IntPtr.Zero ) CloseHandle(token);
return wid;
}
public static WindowsIdentity WIDFromToken( IntPtr src ) {
WindowsIdentity wid = null;
IntPtr token = IntPtr.Zero;
if( DuplicateToken(src, IMPERSONATION_LEVEL_IMPERSONATE, ref token) != 0 ) {
wid = new WindowsIdentity(token);
}
if( token != IntPtr.Zero ) CloseHandle(token);
return wid;
}
private static void split_username( string username, ref string username_out, ref string domain_out ) {
string[] composite_username = username.Split(new char[] { '\\' });
if( composite_username.Length == 2 ) {
domain_out = composite_username[0];
username_out = composite_username[1];
}
}
}
Powershell助手类:
public class PSHelper {
public static Runspace new_runspace() {
InitialSessionState init_state = InitialSessionState.CreateDefault();
init_state.ThreadOptions = PSThreadOptions.UseCurrentThread;
init_state.ImportPSModule(new[] { "ActiveDirectory" });
return RunspaceFactory.CreateRunspace(init_state);
}
}
ASP.NET表单处理程序:
protected void LookupButton_Click( object sender, EventArgs e ) {
string outp = "";
WindowsIdentity wid = IdentityHelper.Logon(Request["AUTH_USER"], Request["AUTH_PASSWORD"]);
using( wid.Impersonate() ) {
Runspace runspace;
Pipeline pipe;
runspace = PSHelper.new_runspace();
runspace.Open();
pipe = runspace.CreatePipeline();
Command cmd = new Command("Get-ADUser");
cmd.Parameters.Add(new CommandParameter("Identity", text_username.Text));
pipe.Commands.Add(cmd);
outp = pipe.Invoke().First().Properties["Name"].Value.ToString();
}
output.Text = outp;
}
答案 1 :(得分:0)
在导入ActiveDirectory模块和尝试执行命令之间添加延迟是否有帮助?