c# - Jwt 令牌过期后如何断开 SignalR Hub 客户端
问题描述
我一直在玩微软的 ChatHub 示例,以了解有关新发布的 net-core signalR 的一些知识。我实现了 Jwt 身份验证并将 Authorize 添加到我的 Hub。我配置了我的 Jwt 身份验证来验证过期。但是,如果客户端在令牌有效时成功连接到集线器。即使在令牌过期后它仍然保持连接。客户端无法将任何请求发布到同一服务器的其他端点,但会获取所有推送通知。你可以在这里看到我的游乐场
我的问题是:在 Jwt 令牌过期后是否有任何解决方法可以断开客户端?
解决方案
您必须自己跟踪连接。
这是可在您提供的代码中使用的连接存储示例
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();
}
}
这只是了解这个想法的一个例子。修改它以满足您的需求。我希望它会有所帮助)
推荐阅读
- python - Python PyGame 制作蛇游戏
- git - “git merge”——已经是最新的
- python - 具有“修改”中位数的 CSV pandas groupby
- python - 无法使用 cv.imread()
- php - 如何在从 php laravel 中的控制器上传之前显示图像预览
- asp.net - .net docx中的新行和sapce字符?
- java - Android Chronometer 秒表以 0.25 的速度滴答作响
- localserver - 无法在 Ampps 3.9 中创建新域
- javascript - ReactJs 向这个按钮添加类
- xslt-1.0 - 如何在 xslt 中使用 GrandChild 的值