简体   繁体   中英

Restrict number of concurrent wcf sessions per user

I have a WCF service that is session-based and secure (probably will use federated security).

I would like to allow only 1 concurrent session per user. For instance bob can open a session and communicated with the server, but if he tries to open another session without closing the first one, he will not succeed.

Does WCF support this out of the box? Thanks!

I can't think of any way to do it automatically. But you could do ti manually by keeping a list of sessions ( OperationContext.Current.Channel.SessionId ) and their associated users in memory in your service, perhaps like this:

private static Dictionary<string, Guid> activeSessions = new Dictionary<string, Guid>();

Before you process any request, check to see if that user has an entry with a different Guid. If the user does, throw a fault. If the user doesn't, add the User/Session to your dictionary. Then add a handler to the OperationContext.Current.Channel.OnClosed event that removes the entry when the user departs. Perhaps like so:

OperationContext.Current.Channel.OnClosed += (s, e) =>
    {
        Guid sessionId = ((IContextChannel)sender).SessionId;
        var entry = activeSessions.FirstOrDefault(kvp => kvp.Value == sessionId);
        activeSessions.Remove(entry.Key);
    };

Basically, you need to do following:

  1. Configure security on client and service binding so identity is transferred to service.
  2. Implement custom authorization manager that will track sessions and allow/forbid user to use the service.
  3. Configure serviceAuthorization behavior to use implemented authorization manager.

See following configuration and code samples.

Client configuration :

<system.serviceModel>
    <client>
        <endpoint name="NetTcpBinding_IService"
                  address="net.tcp://localhost:13031/Service"
                  binding="netTcpBinding" bindingConfiguration="TCP"
                  contract="Common.IService"/>
    </client>
    <bindings>
        <netTcpBinding>
            <binding name="TCP">
                <security mode="Transport">
                    <transport clientCredentialType="Windows" />
                </security>
            </binding>
        </netTcpBinding>
    </bindings>
</system.serviceModel>

Service configuration :

<system.serviceModel>
    <services>
        <service name="Server.Service" behaviorConfiguration="customAuthorization">
            <endpoint address="net.tcp://localhost:13031/Service"
                      binding="netTcpBinding" bindingConfiguration="TCP"
                      contract="Common.IService" />
        </service>
    </services>
    <bindings>
        <netTcpBinding>
            <binding name="TCP">
                <security mode="Transport">
                    <transport clientCredentialType="Windows" />
                </security>
            </binding>
        </netTcpBinding>
    </bindings>
    <behaviors>
        <serviceBehaviors>
            <behavior name="customAuthorization">
                <serviceAuthorization 
                    serviceAuthorizationManagerType="Extensions.SingleSessionPerUserManager, Extensions"/>
            </behavior>
        </serviceBehaviors>
    </behaviors>
</system.serviceModel>

Custom authorization manager :

public class SingleSessionPerUserManager : ServiceAuthorizationManager
{
    private SessionStorage Storage { get; set; }

    public SingleSessionPerUserManager()
    {
        Storage = new SessionStorage();
    }

    protected override bool CheckAccessCore( OperationContext operationContext )
    {
        string name = operationContext.ServiceSecurityContext.PrimaryIdentity.Name;
        if ( Storage.IsActive( name ) )
            return false;

        Storage.Activate( operationContext.SessionId, name );
        operationContext.Channel.Closed += new EventHandler( Channel_Closed );
        return true;
    }

    private void Channel_Closed( object sender, EventArgs e )
    {
        Storage.Deactivate( ( sender as IContextChannel ).SessionId );
    }
}

The helper class used to track session information :

public class SessionStorage
{
    private Dictionary<string, string> Names { get; set; }

    public SessionStorage()
    {
        Names = new Dictionary<string, string>();
    }

    public void Activate( string sessionId, string name )
    {
        Names[ name ] = sessionId;
    }

    public void Deactivate( string sessionId )
    {
        string name = ( from n in Names where n.Value == sessionId select n.Key ).FirstOrDefault();
        if ( name == null )
            return;

        Names.Remove( name );
    }

    public bool IsActive( string name )
    {
        return Names.ContainsKey( name );
    }
}

EDIT : After first session is activated, each following request for a session will cause a System.ServiceModel.Security.SecurityAccessDeniedException: Access is denied. exception to be thrown.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM