3

我有一个基于 netTcpBinding 的双工 WCF 服务,该服务使用订阅模型,其中客户端将使用订阅/取消订阅方法通过回调实现订阅服务。

该服务有一个自定义 IAuthorizationPolicy,它对来自数据库的用户进行身份验证,并将默认线程主体设置为 IAuthorizationPolicy.Evaluate() 方法中的自定义主体。

该服务在 IIS 7.5 中作为服务运行,在 LOCALSYSTEM 身份下,Windows 7

首先,这是客户端(WinForms)对用户进行身份验证的方式:

  1. 显示登录表单以要求提供凭据
  2. 在不同的端点上调用另一个身份验证服务来验证用户凭据
  3. 使用 AppDomain.SetThreadPrincipal 设置默认线程主体

我面临的问题是,在回调线程中,并且仅在回调线程中, Thread.CurrentPrincipal 被重置为匿名 GenericPrincipal 对象,并且我在身份验证后设置的自定义主体不会传播。


更新:如果我将securityMode和clientCredentialType设置为“None”,则主体会按预期传播,显然这与安全配置有关,我尝试了很多配置组合但没有运气。


更新:我已经包含了一个重现该问题的示例项目。

该解决方案简单明了,一切都已配置并运行,只需点击几下,您的宝贵时间只需几分钟,它包含 5 个项目:

  1. PM.Services - WCF 服务项目
  2. PM.Contracts - 包含服务合同的库
  3. Client/Broadcaster - 控制台应用程序通过服务向其他客户端广播。
  4. 客户端/侦听器- 订阅服务/侦听广播的控制台应用程序(这是重置 Thread.CurrentPrincipal 的位置)
  5. Client/Shared - 两个客户端之间共享的库,包含回调实现和服务创建代码。

要在您的机器上配置服务(假设机器上已启用 IIS7 或更高版本),解决方案根文件夹包含两个批处理文件:

InstallService.bat:这批将:

  • 在您的计算机上启用以下 Windows 功能及其父功能(如果尚未启用): Windows Communication Foundation HTTP ActivationWindows 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
4

0 回答 0