我在使用 C# Windows 服务中的 Windows 模拟通过 net.pipe 调用 WCF 服务时遇到问题。
背景
该服务从队列中读取并创建子应用程序域,每个子应用程序域针对从队列中拉出的项目运行特定的模块。我们将 Windows 服务称为“JobQueueAgent”,将每个模块称为“Job”。今后我将使用这些术语。可以将作业配置为以指定用户身份运行。我们在作业的应用程序域内使用模拟来实现此目的。 以下是服务中的逻辑和凭证流程:
JobQueueAgent(Windows 服务 – 主用户)>> 创建作业域 >> 作业域(应用程序域)>> 模拟子用户>> 通过模拟在线程上运行作业 >> 作业(模块-子用户)>>作业逻辑
“主用户”和“子用户”都是具有“作为服务登录”权限的域帐户。
该服务在运行 Windows Server 2012 R2 的虚拟服务器上运行。
以下是我正在使用的 C# 模拟代码:
namespace JobQueue.WindowsServices
{
using System;
using System.ComponentModel;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Permissions;
using System.Security.Principal;
internal sealed class ImpersonatedIdentity : IDisposable
{
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public ImpersonatedIdentity(NetworkCredential credential)
{
if (credential == null) throw new ArgumentNullException("credential");
if (LogonUser(credential.UserName, credential.Domain, credential.Password, 5, 0, out _handle))
{
_context = WindowsIdentity.Impersonate(_handle);
}
else
{
throw new AuthenticationException("Impersonation failed.", newWin32Exception(Marshal.GetLastWin32Error()));
}
}
~ImpersonatedIdentity()
{
Dispose();
}
public void Dispose()
{
if (_handle != IntPtr.Zero)
{
CloseHandle(_handle);
_handle = IntPtr.Zero;
}
if (_context != null)
{
_context.Undo();
_context.Dispose();
_context = null;
}
GC.SuppressFinalize(this);
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool LogonUser(string userName, string domain, string password, int logonType,int logonProvider, out IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
private IntPtr _handle = IntPtr.Zero;
private WindowsImpersonationContext _context;
}
}
问题
某些作业需要对服务器上运行的另一个 Windows 服务进行 net.pipe WCF 服务调用。在模拟下运行时,net.pipe 调用失败。
这是我在这种情况下遇到的异常:
Unhandled Exception: System.ComponentModel.Win32Exception: Access is denied
Server stack trace: at System.ServiceModel.Channels.AppContainerInfo.GetCurrentProcessToken() at System.ServiceModel.Channels.AppContainerInfo.RunningInAppContainer() at System.ServiceModel.Channels.AppContainerInfo.get_IsRunningInAppContainer() at System.ServiceModel.Channels.PipeSharedMemory.BuildPipeName(String pipeGuid)
当不在模拟下运行时,net.pipe 会成功。当模拟用户添加到管理员组时,net.pipe 调用也会成功。这意味着用户需要一些权限才能在模拟状态下进行调用。我们无法确定用户在模拟时进行 net.pipe 调用所需的策略、权限或访问权限。将用户设置为管理员是 Not Acceptable 。
这是一个已知问题吗?用户是否需要特定的权利才能成功?我可以更改代码来解决此问题吗? Using WCF's net.pipe in a website with impersonate=true似乎表明由于 NetworkService,这在 ASP.NET 应用程序中不起作用。不确定,但这不应该适用于此。
最佳答案
在 Microsoft 支持的帮助下,我能够通过修改线程标识的访问权限来解决此问题(Harry Johnston 在另一个答案中建议的内容)。这是我现在使用的模拟代码:
using System;
using System.ComponentModel;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Authentication;
using System.Security.Permissions;
using System.Security.Principal;
internal sealed class ImpersonatedIdentity : IDisposable
{
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public ImpersonatedIdentity(NetworkCredential credential)
{
if (credential == null) throw new ArgumentNullException(nameof(credential));
_processIdentity = WindowsIdentity.GetCurrent();
var tokenSecurity = new TokenSecurity(new SafeTokenHandleRef(_processIdentity.Token), AccessControlSections.Access);
if (!LogonUser(credential.UserName, credential.Domain, credential.Password, 5, 0, out _token))
{
throw new AuthenticationException("Impersonation failed.", new Win32Exception(Marshal.GetLastWin32Error()));
}
_threadIdentity = new WindowsIdentity(_token);
tokenSecurity.AddAccessRule(new AccessRule<TokenRights>(_threadIdentity.User, TokenRights.TOKEN_QUERY, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow));
tokenSecurity.ApplyChanges();
_context = _threadIdentity.Impersonate();
}
~ImpersonatedIdentity()
{
Dispose();
}
public void Dispose()
{
if (_processIdentity != null)
{
_processIdentity.Dispose();
_processIdentity = null;
}
if (_token != IntPtr.Zero)
{
CloseHandle(_token);
_token = IntPtr.Zero;
}
if (_context != null)
{
_context.Undo();
_context.Dispose();
_context = null;
}
GC.SuppressFinalize(this);
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool LogonUser(string userName, string domain, string password, int logonType, int logonProvider, out IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
private WindowsIdentity _processIdentity;
private WindowsIdentity _threadIdentity;
private IntPtr _token = IntPtr.Zero;
private WindowsImpersonationContext _context;
[Flags]
private enum TokenRights
{
TOKEN_QUERY = 8
}
private class TokenSecurity : ObjectSecurity<TokenRights>
{
public TokenSecurity(SafeHandle safeHandle, AccessControlSections includeSections)
: base(false, ResourceType.KernelObject, safeHandle, includeSections)
{
_safeHandle = safeHandle;
}
public void ApplyChanges()
{
Persist(_safeHandle);
}
private readonly SafeHandle _safeHandle;
}
private class SafeTokenHandleRef : SafeHandle
{
public SafeTokenHandleRef(IntPtr handle)
: base(IntPtr.Zero, false)
{
SetHandle(handle);
}
public override bool IsInvalid
{
get { return handle == IntPtr.Zero || handle == new IntPtr(-1); }
}
protected override bool ReleaseHandle()
{
throw new NotImplementedException();
}
}
}
关于.net - 如何在模拟 Windows 服务时调用 net.pipe(命名管道)WCF 服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33022583/