【问题标题】:Restrict number of concurrent wcf sessions per user限制每个用户的并发 wcf 会话数
【发布时间】:2011-06-24 19:55:02
【问题描述】:

我有一个基于会话且安全的 WCF 服务(可能会使用联合安全性)。

我希望每个用户只允许 1 个并发会话。例如 bob 可以打开一个会话并与服务器通信,但如果他尝试打开另一个会话而不关闭第一个会话,他将不会成功。

WCF 是否支持此功能? 谢谢!

【问题讨论】:

  • 为什么要这样做?会议那么贵吗?
  • 例如,我不希望用户能够从 2 台不同的机器登录服务,因为可能存在争用(例如,如果您从另一台机器登录到您的 gmail,同样的方式,你被踢出原来的机器)。
  • 实际上,您可以从任意多台机器登录 gmail。但这是一个附带问题:-)。
  • 为什么会有争用?这听起来像是过早的优化。

标签: c# wcf session wcf-security


【解决方案1】:

我想不出任何方法可以自动完成。但是您可以通过在您的服务的内存中保留会话列表 (OperationContext.Current.Channel.SessionId) 及其关联用户来手动执行 ti,可能像这样:

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

在处理任何请求之前,请检查该用户是否有一个具有不同 Guid 的条目。如果用户这样做,则抛出错误。如果用户没有,请将用户/会话添加到您的字典中。然后将处理程序添加到OperationContext.Current.Channel.OnClosed 事件,当用户离开时删除该条目。或许是这样:

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

【讨论】:

  • 这种方法有一个很大的问题:如果客户端在没有正确关闭通道的情况下崩溃,那么在重新启动服务之前,他永远无法重新连接。你有解决这个问题的办法吗?
  • @Chris - 如果客户端在未关闭通道的情况下崩溃,通道仍将关闭。如果您使用像 Net.TCP 这样的面向连接的绑定,它会立即关闭;如果它使用其中一个长轮询绑定,则可能需要一段时间,直到超时。但无论哪种情况,Channel.OnClosed 事件最终都会触发。
【解决方案2】:

基本上,您需要执行以下操作:

  1. 在客户端和服务绑定上配置安全性,以便将身份传输到服务。
  2. 实施自定义授权管理器,该管理器将跟踪会话并允许/禁止用户使用该服务。
  3. 配置 serviceAuthorization 行为以使用实施的授权管理器。

请参阅以下配置和代码示例。

客户端配置

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

服务配置

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

自定义授权管理器

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 );
    }
}

用于跟踪会话信息的助手类

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 );
    }
}

编辑: 激活第一个会话后,每个后续的会话请求都会导致 System.ServiceModel.Security.SecurityAccessDeniedException: Access is denied. 抛出异常。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-11
    • 2015-02-17
    • 1970-01-01
    • 1970-01-01
    • 2018-10-06
    • 2011-08-29
    • 2015-01-01
    相关资源
    最近更新 更多