我有一个基于 netTcpBinding 的双工 WCF 服务,该服务使用订阅模型,其中客户端将使用订阅/取消订阅方法通过回调实现订阅服务。
该服务有一个自定义 IAuthorizationPolicy,它对来自数据库的用户进行身份验证,并将默认线程主体设置为 IAuthorizationPolicy.Evaluate() 方法中的自定义主体。
该服务在 IIS 7.5 中作为服务运行,在 LOCALSYSTEM 身份下,Windows 7
首先,这是客户端(WinForms)对用户进行身份验证的方式:
- 显示登录表单以要求提供凭据
- 在不同的端点上调用另一个身份验证服务来验证用户凭据
- 使用 AppDomain.SetThreadPrincipal 设置默认线程主体
我面临的问题是,在回调线程中,并且仅在回调线程中, Thread.CurrentPrincipal 被重置为匿名 GenericPrincipal 对象,并且我在身份验证后设置的自定义主体不会传播。
更新:如果我将securityMode和clientCredentialType设置为“None”,则主体会按预期传播,显然这与安全配置有关,我尝试了很多配置组合但没有运气。
更新:我已经包含了一个重现该问题的示例项目。
该解决方案简单明了,一切都已配置并运行,只需点击几下,您的宝贵时间只需几分钟,它包含 5 个项目:
- PM.Services - WCF 服务项目
- PM.Contracts - 包含服务合同的库
- Client/Broadcaster - 控制台应用程序通过服务向其他客户端广播。
- 客户端/侦听器- 订阅服务/侦听广播的控制台应用程序(这是重置 Thread.CurrentPrincipal 的位置)。
- Client/Shared - 两个客户端之间共享的库,包含回调实现和服务创建代码。
要在您的机器上配置服务(假设机器上已启用 IIS7 或更高版本),解决方案根文件夹包含两个批处理文件:
InstallService.bat:这批将:
在您的计算机上启用以下 Windows 功能及其父功能(如果尚未启用): Windows Communication Foundation HTTP Activation和Windows Communication Foundation Non-HTTP Activation,以启用 WCF 和 net.tcp。
将在 TrustedPeople 存储中创建和添加服务证书。
将为将侦听的服务创建一个网站:7359 (http) 和 7357 (net.tcp)
将在 LOCALSYSTEM 身份下创建一个 ApplicationPool (以避免在测试过程中出现权限问题)
将在 c:\inetpub\WCFCallbackSample 中创建一个文件夹并将服务输出复制到其中。
UninstallService.bat:此批次将:
从 TrustedPeople 存储中删除服务证书。
卸载服务网站。
删除网站文件夹。
运行示例解决方案:
构建解决方案
使用 InstallService.bat 安装服务。
调试/运行解决方案以运行两个服务消费者(Client\Broadcaster、Client\Listener)
从广播者发送消息
在侦听器中,您会注意到回调线程标识(打印在控制台上)与应用程序启动时 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();
}
});
}
}
更新回调方法的调用堆栈
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