3

netTcpBinding を介した二重 WCF サービスがあります。このサービスは、クライアントが Subscribe/Unsubscribe メソッドを使用したコールバック実装でサービスにサブスクライブするサブスクリプション モデルを使用します。

このサービスには、データベースからユーザーを認証し、IAuthorizationPolicy.Evaluate() メソッドでデフォルトのスレッド プリンシパルをカスタム プリンシパルに設定するカスタム IAuthorizationPolicy があります。

このサービスは、Windows 7 の LOCALSYSTEM ID の下で、IIS 7.5 のサービスとして実行されています。

まず、クライアント (WinForms) がユーザーを認証する方法は次のとおりです。

  1. ログオン フォームを表示して資格情報を要求する
  2. 別のエンドポイントで別の認証サービスを呼び出して、ユーザー資格情報を検証する
  3. AppDomain.SetThreadPrincipal を使用して既定のスレッド プリンシパルを設定する

私が直面している問題は、コールバック スレッドで、コールバック スレッドでのみ、Thread.CurrentPrincipal が匿名の GenericPrincipal オブジェクトにリセットされ、認証後に設定されたカスタム プリンシパルが伝達されないことです。


更新: securityMode と clientCredentialType を "None" に設定すると、プリンシパルは期待どおりに伝達されます。明らかに、これはセキュリティ構成と関係があります。構成の組み合わせをたくさん試しましたが、うまくいきませんでした。


更新:問題を再現するサンプル プロジェクトを含めました。

このソリューションは、すべてが構成され実行されているので簡単で、数回クリックするだけで貴重な時間が数分かかります。5 つのプロジェクトが含まれています。

  1. PM.Services - WCF サービス プロジェクト
  2. PM.Contracts - サービス コントラクトを含むライブラリ
  3. クライアント/ブロードキャスター- コンソール アプリケーションは、サービスを通じて他のクライアントにブロードキャストします。
  4. クライアント/リスナー- サービスにサブスクライブする/ブロードキャストをリッスンするコンソール アプリケーション(ここで Thread.CurrentPrincipal がリセットされます)
  5. Client/Shared - コールバックの実装とサービス作成コードを含む 2 つのクライアント間で共有されるライブラリ。

マシンでサービスを構成するには (マシンで IIS7 以降が既に有効になっていると仮定)、ソリューション ルート フォルダーには次の 2 つのバッチ ファイルが含まれます。

InstallService.bat:このバッチは:

  • コンピューターで次の Windows 機能とその親機能を有効にします (まだ有効になっていない場合): Windows Communication Foundation HTTP ActivationWindows Communication Foundation Non-HTTP Activation。WCF と net.tcp を有効にします。

  • TrustedPeople ストアにサービス証明書を作成して追加します。

  • 7359 (http) および 7357 (net.tcp) でリッスンするサービスの Web サイトを作成します。

  • LOCALSYSTEM ID の下に ApplicationPool を作成します (テスト中のアクセス許可の問題を回避するため)。

  • c:\inetpub\WCFCallbackSample にフォルダーを作成し、サービスの出力をそこにコピーします。

UninstallService.bat : このバッチは次のことを行います。

  • TrustedPeople ストアからサービス証明書を削除します。

  • サービス Web サイトをアンインストールします。

  • Web サイト フォルダーを削除します。

サンプル ソリューションの実行:

  • ソリューションを構築する

  • InstallService.bat を使用してサービスをインストールします。

  • ソリューションをデバッグ/実行して、2 つのサービス コンシューマー (Client\Broadcaster、Client\Listener) を実行します。

  • ブロードキャスターからメッセージを送信する

  • リスナーでは、(コンソールに表示される) コールバック スレッドの ID が、アプリケーションの起動時に SetThreadPrincipal によって設定されたメイン スレッドの ID とは異なることがわかります。


最初の投稿のコード:

サービス インターフェイス:

[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/>

UPDATEファクトリ メソッドを使用してサービスを作成します。

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
4

0 に答える 0