Как отключить SignalR Hub Client после истечения срока действия токена Jwt - PullRequest
0 голосов
/ 06 ноября 2018

Я играл на примере ChatHub от Microsoft, чтобы получить некоторые знания о недавно выпущенном net-core signalR. Я реализовал аутентификацию Jwt и добавил Авторизацию к моему Хабу. И я настроил мою аутентификацию Jwt для проверки срока действия. Но если клиент успешно подключается к концентратору, пока токен действителен. Он остается подключенным даже после истечения срока действия токена. Клиент не может отправить любой запрос к другим конечным точкам того же сервера, но получает все push-уведомления. Вы можете увидеть мою игровую площадку здесь

У меня вопрос : есть ли обходной путь для отключения клиента после истечения срока действия токена Jwt?

Ответы [ 2 ]

0 голосов
/ 14 ноября 2018

Вы должны самостоятельно отслеживать соединения.

Вот пример хранилища соединений, которое можно использовать в предоставленном вами коде

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace SignalRServer.API.Hubs
{
  public class HubConnectionsStorage
  {
    private readonly Dictionary<string, HashSet<string>> _connectionsByJwtToken;
    private readonly Dictionary<string, string> _jwtTokenByConnection;
    private readonly Dictionary<string, HashSet<string>> _connectionsByGroup;
    private readonly Dictionary<string, HashSet<string>> _groupsByConnection;
    private readonly ReaderWriterLockSlim _lock;

    public HubConnectionsStorage()
    {
      _connectionsByJwtToken = new Dictionary<string, HashSet<string>>();
      _jwtTokenByConnection = new Dictionary<string, string>();
      _connectionsByGroup = new Dictionary<string, HashSet<string>>();
      _groupsByConnection = new Dictionary<string, HashSet<string>>();
      _lock = new ReaderWriterLockSlim();
    }

    public void AddConnection(string connectionId, string jwtToken)
    {
      _lock.EnterWriteLock();

      try
      {
        _jwtTokenByConnection[connectionId] = jwtToken;

        if (!_connectionsByJwtToken.TryGetValue(jwtToken, out var connections))
          _connectionsByJwtToken[jwtToken] = connections = new HashSet<string>();

        connections.Add(connectionId);
      }
      finally
      {
        _lock.ExitWriteLock();
      }
    }

    public void AddConnectionToGroup(string connectionId, string group)
    {
      _lock.EnterWriteLock();

      try
      {
        if(!_connectionsByGroup.TryGetValue(group, out var connections))
          _connectionsByGroup[group] = connections = new HashSet<string>();

        connections.Add(connectionId);

        if (!_groupsByConnection.TryGetValue(connectionId, out var groups))
          _groupsByConnection[connectionId] = groups = new HashSet<string>();

        groups.Add(group);

      }
      finally
      {
        _lock.ExitWriteLock();
      }
    }

    public void RemoveConnectionFromGroup(string connectionId, string group)
    {
      _lock.EnterWriteLock();

      try
      {
        if (!_connectionsByGroup.TryGetValue(group, out var connections))
          return;

        if(!connections.Remove(connectionId))
          return;

        if (connections.Count == 0)
          _connectionsByGroup.Remove(group);

        var groups = _groupsByConnection[connectionId];

        groups.Remove(group);

        if (groups.Count == 0)
          _groupsByConnection.Remove(connectionId);
      }
      finally
      {
        _lock.ExitWriteLock();
      }
    }

    public void RemoveConnection(string connectionId)
    {
      _lock.EnterWriteLock();

      try
      {
        if(!_jwtTokenByConnection.TryGetValue(connectionId, out var jwtToken))
          return;

        _jwtTokenByConnection.Remove(connectionId);

        var jwtConnections = _connectionsByJwtToken[jwtToken];

        jwtConnections.Remove(connectionId);

        if (jwtConnections.Count == 0)
          _connectionsByJwtToken.Remove(jwtToken);

        if(!_groupsByConnection.TryGetValue(connectionId, out var groups))
          return;

        foreach (var group in groups)
        {
          var connections = _connectionsByGroup[group];
          connections.Remove(connectionId);

          if (connections.Count == 0)
            _connectionsByGroup.Remove(group);
        }

        _groupsByConnection.Remove(connectionId);
      }
      finally
      {
        _lock.ExitWriteLock();
      }
    }

    public List<string> GetGroupConnections(string group)
    {
      _lock.EnterReadLock();

      try
      {
        if (_connectionsByGroup.TryGetValue(group, out var connections))
          return connections.ToList();

        return new List<string>();
      }
      finally 
      {
        _lock.ExitReadLock();
      }
    }

    public void RemoveExpiredConnections(Func<string, bool> validateJwtToken)
    {
      _lock.EnterWriteLock();

      try
      {
        foreach (var jwtToken in _connectionsByJwtToken.Keys.ToList())
        {
          var isValid = validateJwtToken(jwtToken);

          if (isValid) 
            continue;

          var invalidConnections = _connectionsByJwtToken[jwtToken];

          foreach (var invalidConnection in invalidConnections)
          {
            if (_groupsByConnection.TryGetValue(invalidConnection, out var connectionGroups))
            {
              foreach (var group in connectionGroups)
              {
                var groupConnections = _connectionsByGroup[@group];
                groupConnections.Remove(invalidConnection);

                if (groupConnections.Count == 0)
                  _connectionsByGroup.Remove(@group);
              }

              _groupsByConnection.Remove(invalidConnection);
            }

            _jwtTokenByConnection.Remove(invalidConnection);
          }

          _connectionsByJwtToken.Remove(jwtToken);
        }
      }
      finally 
      {
        _lock.ExitWriteLock();
      }
    }
  }
}

Вы можете передать его как синглтон своему концентратору

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using SignalRServer.API.Services;

namespace SignalRServer.API.Hubs
{
[Authorize]
public class NewsHub : Hub
{
private readonly NewsService newsService;
private readonly HubConnectionsStorage connectionsStorage;

public NewsHub(NewsService newsService, HubConnectionsStorage connectionsStorage)
{
  this.newsService = newsService;
  this.connectionsStorage = connectionsStorage;
}

public override Task OnConnectedAsync()
{
  var jwtToken = GetCurrentConnectionJwtToken();
  connectionsStorage.AddConnection(Context.ConnectionId, jwtToken);
  return Task.CompletedTask;
}

public override Task OnDisconnectedAsync(Exception exception)
{
  connectionsStorage.RemoveConnection(Context.ConnectionId);
  return Task.CompletedTask;
}

public async Task Send((string groupName, string generatedNews) news)
{
  if (!newsService.CheckTopic(news.groupName))
    throw new Exception("cannot send a news item to a group which does not exist.");

  connectionsStorage.RemoveExpiredConnections(ValidateJwtToken);

  var groupConnections = connectionsStorage.GetGroupConnections(news.groupName);
  await Clients.Clients(groupConnections).SendAsync("NewsFeed", news.generatedNews);
}

public async Task JoinGroup(string groupName)
{
  if (!newsService.CheckTopic(groupName))
    throw new Exception("cannot join a group which does not exist.");

  connectionsStorage.AddConnectionToGroup(Context.ConnectionId, groupName);

  var groupConnections = connectionsStorage.GetGroupConnections(groupName);

  await Clients.Clients(groupConnections).SendAsync("JoinGroup", groupName);

  var history = newsService.GetTopicNews(groupName);
  await Clients.Client(Context.ConnectionId).SendAsync("History", history);
}

public async Task LeaveGroup(string groupName)
{
  if (!newsService.CheckTopic(groupName))
    throw new Exception("cannot leave a group which does not exist.");

  var groupConnections = connectionsStorage.GetGroupConnections(groupName);

  await Clients.Clients(groupConnections).SendAsync("LeaveGroup", groupName);
  connectionsStorage.RemoveConnectionFromGroup(Context.ConnectionId, groupName);
}

private string GetCurrentConnectionJwtToken() => "fake jwt token "+Random.Next(4);
private bool ValidateJwtToken(string jwtToken) => Random.NextDouble() >= 0.5;

private static readonly Random Random = new Random();
}
}

Это всего лишь пример, чтобы понять идею. Измените его в соответствии с вашими потребностями. Надеюсь это поможет)

0 голосов
/ 09 ноября 2018

Единственное решение, которое я могу придумать, - это сопоставление соединений и токенов. Выполните итерацию по токенам и, если срок их действия истек, отключите соответствующего клиента.

...