我在netTcpBinding上有一个双工WCF服务,该服务在客户端使用订阅模型 将使用Subscribe / Unsubscribe方法通过回调实现订阅服务。
该服务具有自定义IAuthorizationPolicy,用于对数据库中的用户进行身份验证 在IAuthorizationPolicy.Evaluate()方法中将默认线程主体设置为自定义主体。
该服务在IIS 7.5中以LOCALSYSTEM标识,Windows 7
运行首先,客户端(WinForms)如何对用户进行身份验证:
我面临的问题是,在回调线程中,并且只在回调线程中,将Thread.CurrentPrincipal重置为匿名GenericPrincipal对象,并且我的自定义主体在身份验证后未传播。
UPDATE:如果我将securityMode和clientCredentialType设置为“None”,则会按预期传播主体,显然这与安全配置有关,我尝试了很多配置组合而没有运气
更新:我添加了sample project来重现此问题。
解决方案是直接的,所有配置和运行它只需几次点击和几分钟的宝贵时间,它包含5个项目:
要在您的计算机上配置服务(假设已在计算机上启用IIS7或更高版本),解决方案根文件夹包含两个批处理文件:
InstallService.bat:此批次将:
在您的计算机上启用以下Windows功能及其父功能(如果尚未启用): Windows Communication Foundation HTTP激活和 Windows 通信基础非HTTP激活,以启用WCF和 的net.tcp。
将在TrustedPeople商店中创建和添加服务证书。
将为将要收听的服务创建一个网站:7359 (http)和7357(net.tcp)
将在LOCALSYSTEM身份下创建一个ApplicationPool(以避免 测试期间的许可问题)
将在c:\ inetpub \ WCFCallbackSample中创建一个文件夹并将服务输出复制到该文件夹。
UninstallService.bat :此批次将:
从TrustedPeople商店中删除服务证书。
卸载服务网站。
删除网站文件夹。
运行示例解决方案:
构建解决方案
使用InstallService.bat安装服务。
调试/运行解决方案以运行两个服务使用者 (Client \ Broadcaster,Client \ Listener)
从Broadcaster发送消息
在监听器中,您将注意到回调线程标识 (打印在控制台上)与主线程的标识不同 在应用程序启动时由SetThreadPrincipal设置。
初始帖子的代码:
服务界面:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IFinanceServiceCallback))]
public interface IFinanceService : IServiceContract, ISupportCallback // <-- Interface that defines Subscribe and Unsubscribe methods
{
[OperationContract]
void DuplexMessageTest(string text);
}
服务实施:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
public class FinanceService : IFinanceService
{
// Subscription boilerplate code
private static readonly
DuplexServiceSubscriptionManager<IFinanceServiceCallback> _subscriptionManager = new DuplexServiceSubscriptionManager<IFinanceServiceCallback>();
public bool Subscribe()
{
return _subscriptionManager.Subscribe();
}
public bool Unsubscribe()
{
return _subscriptionManager.Unsubscribe();
}
// A test method for simulation
public void DuplexMessageTest(string text)
{
_subscriptionManager.InvokeCallbackMethod(callback => callback.OnDuplexTest(text));
}
回调实施:
[CallbackBehavior(UseSynchronizationContext = false, ConcurrencyMode = ConcurrencyMode.Single, AutomaticSessionShutdown=true)]
public class FinanceServiceCallbackSubscription : BaseServiceSubscription<IFinanceService>, IFinanceServiceCallback
{
public FinanceServiceCallbackSubscription() : base()
{
}
public delegate void DuplexTestEventHandler(string message);
public event DuplexTestEventHandler DuplexTest;
public void OnDuplexTest(string message)
{
-->> At this point, the Thread.CurrentPrincipal is reset <<--
if (DuplexTest != null)
DuplexTest(message);
}
}
更新服务配置
<system.serviceModel>
<services>
...
<service behaviorConfiguration="PM.Behaviour_01" name="PM.Services.FinanceService">
<endpoint address="" binding="netTcpBinding" bindingConfiguration="PMStandardBindingConfiguration"
contract="PM.Contracts.IFinanceService" />
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
</service>
...
<services/>
<bindings>
...
<netTcpBinding>
<binding name="PMStandardBindingConfiguration"
closeTimeout="00:01:00"
openTimeout="00:01:00"
receiveTimeout="00:10:00"
sendTimeout="00:10:00"
maxReceivedMessageSize="2147483647"
maxBufferPoolSize="0"
transferMode="Buffered"
portSharingEnabled="false">
<readerQuotas maxDepth="2147483647"
maxStringContentLength="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="2147483647"
maxNameTableCharCount="16384" />
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
<reliableSession ordered="true" inactivityTimeout="10675199.02:48:05.4775807" />
</binding>
</netTcpBinding>
...
<bindings/>
<behaviors>
...
<behavior name="PM.Behaviour_01">
<serviceMetadata httpGetEnabled="false" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="PM.Services.Security.UserValidator, PM.Services" />
<serviceCertificate findValue="PMSV" storeLocation="LocalMachine" storeName="TrustedPeople" x509FindType="FindBySubjectName" />
<clientCertificate>
<authentication certificateValidationMode="PeerTrust" />
</clientCertificate>
</serviceCredentials>
<serviceAuthorization principalPermissionMode="Custom">
<authorizationPolicies>
<add policyType='PM.Services.Security.PMAuthorizationPolicy, PM.Services' />
</authorizationPolicies>
</serviceAuthorization>
<ErrorHandler /> <!--Error handling behaviour-->
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
...
<behaviors/>
<system.serviceModel/>
更新工厂方法我用以下方法创建服务:
public static ServiceClient<TServiceContract> CreateService<TServiceContract>(object callbackInstance, string username, string password)
where TServiceContract: IServiceContract
{
Binding binding = STANDARD_BINDING;
bool supplyCredentials = true;
Type callbackImplementationType = null;
string protocol = "http";
string addr = "";
int port = PM_STANDARD_HTTP_PORT;
if (BINDING_MAPPING.ContainsKey(typeof(TServiceContract)))
{
var args = BINDING_MAPPING[typeof(TServiceContract)];
binding = (Binding)args[0];
protocol = (string)args[1];
object callbackType = args[2];
if (callbackType != null)
callbackImplementationType = (Type)callbackType;
supplyCredentials = (bool)args[3];
}
// Convention: Service .svc file in url is the same as contract
// without the leading "I"
// e.g. IFinanceService becomes FinanceService
// so url becomes
// http://localhost/PMServices/FinanceService.svc
string serviceName = typeof(TServiceContract).Name.Substring(1); // Remove leading I from interface name
string baseUri = Settings.GetString(iSetting.ServiceBaseURI);
UriBuilder b = new UriBuilder(baseUri);
if (protocol == "net.tcp")
port = PM_STANDARD_TCP_PORT;
addr = string.Format("{0}://{1}:{2}/{3}/{4}.svc",
protocol,
b.Host,
port,
b.Path.Replace("/", ""),
serviceName);
EndpointIdentity identity =
EndpointIdentity.CreateDnsIdentity("PMSV");
EndpointAddress endpointAddress =
new EndpointAddress(new Uri(addr), identity);
ChannelFactory<TServiceContract> channelFactory = null;
// Check whether service is duplex
if((binding.GetType() == typeof(NetTcpBinding) || binding.GetType() == typeof(WSDualHttpBinding))
&& (callbackImplementationType != null || callbackInstance != null))
{
object callbackImplementation = callbackInstance;
if(callbackImplementation == null && callbackImplementationType != null)
callbackImplementation = Activator.CreateInstance(callbackImplementationType, null);
if (callbackImplementation != null)
channelFactory = new DuplexChannelFactory<TServiceContract>(
callbackImplementation,
binding,
endpointAddress);
else // Create non-duplex channel if no callback implementation is specified
channelFactory = new ChannelFactory<TServiceContract>(binding,
endpointAddress);
}
else
channelFactory = new ChannelFactory<TServiceContract>(binding,
endpointAddress);
if (supplyCredentials)
{
channelFactory.Credentials.UserName.UserName =
username ?? PMClientPrincipal.Current.User.Name;
channelFactory.Credentials.UserName.Password =
password ?? ((PMClientIdentity)PMClientPrincipal
.Current
.User).Password;
}
return new ServiceClient<TServiceContract>(channelFactory,
channelFactory.CreateChannel());
}
PMClientPrincipal是我对IPrincipal的实现, PMClientPrincipal.Current 是一个静态属性,只是将当前线程主体转换为PMClientPrincipal 。
UPDATE 管理客户订阅的类(在服务中使用)
public class DuplexServiceSubscriptionManager<TCallbackImplementation>
{
public DuplexServiceSubscriptionManager()
{
_subscribers = new List<TCallbackImplementation>();
}
private object _syncRoot = new object();
public List<TCallbackImplementation> _subscribers;
private void AddClient(TCallbackImplementation callback)
{
lock (_syncRoot)
{
if (!_subscribers.Contains(callback))
{
_subscribers.Add(callback);
((ICommunicationObject)callback).Closed += OnClientLost;
((ICommunicationObject)callback).Faulted += OnClientLost;
}
}
}
private void RemoveClient(TCallbackImplementation callback)
{
lock (_syncRoot)
{
if (_subscribers.Contains(callback))
{
_subscribers.Remove(callback);
((ICommunicationObject)callback).Closed -= OnClientLost;
((ICommunicationObject)callback).Faulted -= OnClientLost;
}
}
}
void OnClientLost(object sender, EventArgs e)
{
ServiceLogger.Log.DebugFormat("Client lost, reason: {0}", ((ICommunicationObject)sender).State);
if (OperationContext.Current == null)
return;
TCallbackImplementation callback = OperationContext.Current.GetCallbackChannel<TCallbackImplementation>();
RemoveClient(callback);
}
public bool Subscribe()
{
try
{
TCallbackImplementation callback = OperationContext.Current.GetCallbackChannel<TCallbackImplementation>();
AddClient(callback);
ServiceLogger.Log.Debug("Client subscribed.");
return true;
}
catch
{
return false;
}
}
public bool Unsubscribe()
{
try
{
TCallbackImplementation callback = OperationContext.Current.GetCallbackChannel<TCallbackImplementation>();
RemoveClient(callback);
ServiceLogger.Log.Debug("Client unsubscribed.");
return true;
}
catch
{
return false;
}
}
public void InvokeCallbackMethod(Action<TCallbackImplementation> target)
{
_subscribers.ForEach(delegate(TCallbackImplementation callback)
{
if (((ICommunicationObject)callback).State == CommunicationState.Opened)
{
try
{
target.Invoke(callback);
}
catch (Exception)
{
Unsubscribe();
}
}
else
{
Unsubscribe();
}
});
}
}
UPDATE 调用堆栈到回调方法
System.ServiceModel.dll!System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(object instance, object[] inputs, out object[] outputs) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(ref System.ServiceModel.Dispatcher.MessageRpc rpc) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.MessageRpc.Process(bool isOperationContextSet) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Dispatch(ref System.ServiceModel.Dispatcher.MessageRpc rpc, bool isOperationContextSet) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(System.ServiceModel.Channels.RequestContext request, bool cleanThread, System.ServiceModel.OperationContext currentOperationContext) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(System.ServiceModel.Channels.RequestContext request, System.ServiceModel.OperationContext currentOperationContext) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(System.IAsyncResult result) Unknown
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete(System.IAsyncResult result) Unknown
System.ServiceModel.Internals.dll!System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult result) Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously) Unknown
System.ServiceModel.Internals.dll!System.Runtime.InputQueue<System.ServiceModel.Channels.Message>.AsyncQueueReader.Set(System.Runtime.InputQueue<System.ServiceModel.Channels.Message>.Item item) Unknown
System.ServiceModel.Internals.dll!System.Runtime.InputQueue<System.ServiceModel.Channels.Message>.EnqueueAndDispatch(System.Runtime.InputQueue<System.ServiceModel.Channels.Message>.Item item, bool canDispatchOnThisThread) Unknown
System.ServiceModel.Internals.dll!System.Runtime.InputQueue<System.ServiceModel.Channels.Message>.EnqueueAndDispatch(System.ServiceModel.Channels.Message item, System.Action dequeuedCallback, bool canDispatchOnThisThread) Unknown
System.ServiceModel.Internals.dll!System.Runtime.InputQueue<System.__Canon>.EnqueueAndDispatch(System.__Canon item) Unknown
System.ServiceModel.dll!System.ServiceModel.Security.SecuritySessionClientSettings<System.ServiceModel.Channels.IDuplexSessionChannel>.ClientSecurityDuplexSessionChannel.CompleteReceive(System.IAsyncResult result) Unknown
System.ServiceModel.dll!System.ServiceModel.Security.SecuritySessionClientSettings<System.ServiceModel.Channels.IDuplexSessionChannel>.ClientSecurityDuplexSessionChannel.OnReceive(System.IAsyncResult result) Unknown
System.ServiceModel.Internals.dll!System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult result) Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously) Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously, System.Exception exception) Unknown
System.ServiceModel.dll!System.ServiceModel.Security.SecuritySessionClientSettings<System.ServiceModel.Channels.IDuplexSessionChannel>.ClientSecuritySessionChannel.ReceiveAsyncResult.OnReceive(System.IAsyncResult result) Unknown
System.ServiceModel.Internals.dll!System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult result) Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously) Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously, System.Exception exception) Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.ReliableChannelBinder<System.ServiceModel.Channels.IDuplexSessionChannel>.InputAsyncResult<System.ServiceModel.Channels.ReliableChannelBinder<System.ServiceModel.Channels.IDuplexSessionChannel>>.OnInputComplete(System.IAsyncResult result) Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.ReliableChannelBinder<System.ServiceModel.Channels.IDuplexSessionChannel>.InputAsyncResult<System.ServiceModel.Channels.ReliableChannelBinder<System.ServiceModel.Channels.IDuplexSessionChannel>>.OnInputCompleteStatic(System.IAsyncResult result) Unknown
System.ServiceModel.Internals.dll!System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult result) Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously) Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously, System.Exception exception) Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.TransportDuplexSessionChannel.TryReceiveAsyncResult.OnReceive(System.IAsyncResult result) Unknown
System.ServiceModel.Internals.dll!System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult result) Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously) Unknown
System.ServiceModel.Internals.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously, System.Exception exception) Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SynchronizedMessageSource.SynchronizedAsyncResult<System.ServiceModel.Channels.Message>.CompleteWithUnlock(bool synchronous, System.Exception exception) Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SynchronizedMessageSource.ReceiveAsyncResult.OnReceiveComplete(object state) Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SessionConnectionReader.OnAsyncReadComplete(object state) Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SocketConnection.FinishRead() Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SocketConnection.OnReceiveAsync(object sender, System.Net.Sockets.SocketAsyncEventArgs eventArgs) Unknown
System.ServiceModel.dll!System.ServiceModel.Channels.SocketConnection.OnReceiveAsyncCompleted(object sender, System.Net.Sockets.SocketAsyncEventArgs e) Unknown
System.dll!System.Net.Sockets.SocketAsyncEventArgs.OnCompleted(System.Net.Sockets.SocketAsyncEventArgs e) Unknown
System.dll!System.Net.Sockets.SocketAsyncEventArgs.FinishOperationSuccess(System.Net.Sockets.SocketError socketError, int bytesTransferred, System.Net.Sockets.SocketFlags flags) Unknown
System.dll!System.Net.Sockets.SocketAsyncEventArgs.CompletionPortCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* nativeOverlapped) Unknown
mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP) Unknown