Browse Source

feat(identity): Handle user sessions using distributed events

pull/1165/head
colin 10 months ago
parent
commit
b9ec5c9765
  1. 2
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentitySessionRepository.cs
  2. 93
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionCacheItemSynchronizer.cs
  3. 75
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionEntityCreatedEventHandler.cs
  4. 4
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionStore.cs
  5. 2
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/EfCoreIdentitySessionRepository.cs

2
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentitySessionRepository.cs

@ -21,5 +21,5 @@ public interface IIdentitySessionRepository : Volo.Abp.Identity.IIdentitySession
Task<List<IdentitySession>> GetListAsync(Guid userId, CancellationToken cancellationToken = default);
Task DeleteAllAsync(string sessionId, Guid? exceptSessionId = null, CancellationToken cancellationToken = default);
Task DeleteAllSessionAsync(string sessionId, Guid? exceptSessionId = null, CancellationToken cancellationToken = default);
}

93
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionCacheItemSynchronizer.cs

@ -1,4 +1,7 @@
using System;
using LINGYUN.Abp.Identity.Settings;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
@ -6,6 +9,8 @@ using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;
using Volo.Abp.Settings;
using Volo.Abp.Uow;
namespace LINGYUN.Abp.Identity.Session;
public class IdentitySessionCacheItemSynchronizer :
@ -15,15 +20,21 @@ public class IdentitySessionCacheItemSynchronizer :
ILocalEventHandler<EntityDeletedEventData<IdentityUser>>,
ITransientDependency
{
public ILogger<IdentitySessionCacheItemSynchronizer> Logger { protected get; set; }
protected ISettingProvider SettingProvider { get; }
protected IIdentitySessionCache IdentitySessionCache { get; }
protected IIdentitySessionStore IdentitySessionStore { get; }
public IdentitySessionCacheItemSynchronizer(
ISettingProvider settingProvider,
IIdentitySessionCache identitySessionCache,
IIdentitySessionStore identitySessionStore)
{
SettingProvider = settingProvider;
IdentitySessionCache = identitySessionCache;
IdentitySessionStore = identitySessionStore;
Logger = NullLogger<IdentitySessionCacheItemSynchronizer>.Instance;
}
public async virtual Task HandleEventAsync(EntityDeletedEto<IdentitySessionEto> eventData)
@ -31,21 +42,11 @@ public class IdentitySessionCacheItemSynchronizer :
await IdentitySessionCache.RemoveAsync(eventData.Entity.SessionId);
}
[UnitOfWork]
public async virtual Task HandleEventAsync(EntityCreatedEto<IdentitySessionEto> eventData)
{
var identitySessionCacheItem = new IdentitySessionCacheItem(
eventData.Entity.Device,
eventData.Entity.DeviceInfo,
eventData.Entity.UserId,
eventData.Entity.SessionId,
eventData.Entity.ClientId,
eventData.Entity.IpAddresses,
eventData.Entity.SignedIn,
eventData.Entity.LastAccessed);
await IdentitySessionCache.RefreshAsync(
eventData.Entity.SessionId,
identitySessionCacheItem);
await RefreshSessionCache(eventData.Entity);
await CheckConcurrentLoginStrategy(eventData.Entity);
}
public async virtual Task HandleEventAsync(IdentitySessionChangeAccessedEvent eventData)
@ -73,4 +74,68 @@ public class IdentitySessionCacheItemSynchronizer :
// 用户被删除, 移除所有会话
await IdentitySessionStore.RevokeAllAsync(eventData.Entity.Id);
}
protected async virtual Task RefreshSessionCache(IdentitySessionEto session)
{
var identitySessionCacheItem = new IdentitySessionCacheItem(
session.Device,
session.DeviceInfo,
session.UserId,
session.SessionId,
session.ClientId,
session.IpAddresses,
session.SignedIn,
session.LastAccessed);
await IdentitySessionCache.RefreshAsync(
session.SessionId,
identitySessionCacheItem);
}
protected async virtual Task CheckConcurrentLoginStrategy(IdentitySessionEto session)
{
// 创建一个会话后根据策略使其他会话失效
var strategySet = await SettingProvider.GetOrNullAsync(IdentitySettingNames.Session.ConcurrentLoginStrategy);
Logger.LogDebug($"The concurrent login strategy is: {strategySet}");
if (!strategySet.IsNullOrWhiteSpace() && Enum.TryParse<ConcurrentLoginStrategy>(strategySet, true, out var strategy))
{
switch (strategy)
{
// 限制用户相同设备
case ConcurrentLoginStrategy.LogoutFromSameTypeDevicesLimit:
var sameTypeDevicesCountSet = await SettingProvider.GetAsync(IdentitySettingNames.Session.LogoutFromSameTypeDevicesLimit, 1);
Logger.LogDebug($"Clear other sessions on the device {session.Device} and save only {sameTypeDevicesCountSet} sessions.");
await IdentitySessionStore.RevokeWithAsync(
session.UserId,
session.Device,
session.Id,
sameTypeDevicesCountSet);
break;
// 限制登录设备
case ConcurrentLoginStrategy.LogoutFromSameTypeDevices:
Logger.LogDebug($"Clear all other sessions on the device {session.Device}.");
await IdentitySessionStore.RevokeAllAsync(
session.UserId,
session.Device,
session.Id);
break;
// 限制多端登录
case ConcurrentLoginStrategy.LogoutFromAllDevices:
Logger.LogDebug($"Clear all other user sessions.");
await IdentitySessionStore.RevokeAllAsync(
session.UserId,
session.Id);
break;
}
}
}
}

75
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionEntityCreatedEventHandler.cs

@ -1,75 +0,0 @@
using LINGYUN.Abp.Identity.Settings;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
using Volo.Abp.Identity;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Identity.Session;
public class IdentitySessionEntityCreatedEventHandler :
ILocalEventHandler<EntityCreatedEventData<IdentitySession>>,
ITransientDependency
{
public ILogger<IdentitySessionEntityCreatedEventHandler> Logger { protected get; set; }
private readonly ISettingProvider _settingProvider;
private readonly IIdentitySessionStore _identitySessionStore;
private readonly IDistributedCache<IdentitySessionCacheItem> _identitySessionCache;
public IdentitySessionEntityCreatedEventHandler(
ISettingProvider settingProvider,
IIdentitySessionStore identitySessionStore,
IDistributedCache<IdentitySessionCacheItem> identitySessionCache)
{
_settingProvider = settingProvider;
_identitySessionStore = identitySessionStore;
_identitySessionCache = identitySessionCache;
Logger = NullLogger<IdentitySessionEntityCreatedEventHandler>.Instance;
}
public async virtual Task HandleEventAsync(EntityCreatedEventData<IdentitySession> eventData)
{
// 创建一个会话后根据策略使其他会话失效
var strategySet = await _settingProvider.GetOrNullAsync(IdentitySettingNames.Session.ConcurrentLoginStrategy);
Logger.LogDebug($"The concurrent login strategy is: {strategySet}");
if (!strategySet.IsNullOrWhiteSpace() && Enum.TryParse<ConcurrentLoginStrategy>(strategySet, true, out var strategy))
{
switch (strategy)
{
// 限制用户相同设备
case ConcurrentLoginStrategy.LogoutFromSameTypeDevicesLimit:
var sameTypeDevicesCountSet = await _settingProvider.GetAsync(IdentitySettingNames.Session.LogoutFromSameTypeDevicesLimit, 1);
Logger.LogDebug($"Clear other sessions on the device {eventData.Entity.Device} and save only {sameTypeDevicesCountSet} sessions.");
await _identitySessionStore.RevokeWithAsync(
eventData.Entity.UserId,
eventData.Entity.Device,
eventData.Entity.Id,
sameTypeDevicesCountSet);
break;
// 限制登录设备
case ConcurrentLoginStrategy.LogoutFromSameTypeDevices:
Logger.LogDebug($"Clear all other sessions on the device {eventData.Entity.Device}.");
await _identitySessionStore.RevokeAllAsync(
eventData.Entity.UserId,
eventData.Entity.Device,
eventData.Entity.Id);
break;
// 限制多端登录
case ConcurrentLoginStrategy.LogoutFromAllDevices:
Logger.LogDebug($"Clear all other user sessions.");
await _identitySessionStore.RevokeAllAsync(
eventData.Entity.UserId,
eventData.Entity.Id);
break;
}
}
}
}

4
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionStore.cs

@ -126,7 +126,7 @@ public class IdentitySessionStore : IIdentitySessionStore, ITransientDependency
string sessionId,
CancellationToken cancellationToken = default)
{
await IdentitySessionRepository.DeleteAllAsync(sessionId, cancellationToken: cancellationToken);
await IdentitySessionRepository.DeleteAllSessionAsync(sessionId, cancellationToken: cancellationToken);
}
public async virtual Task RevokeAllAsync(
@ -134,7 +134,7 @@ public class IdentitySessionStore : IIdentitySessionStore, ITransientDependency
Guid? exceptSessionId = null,
CancellationToken cancellationToken = default)
{
await IdentitySessionRepository.DeleteAllAsync(userId, exceptSessionId, cancellationToken: cancellationToken);
await IdentitySessionRepository.DeleteAllAsync(userId: userId, exceptSessionId: exceptSessionId, cancellationToken: cancellationToken);
}
public async virtual Task RevokeAllAsync(

2
aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/EfCoreIdentitySessionRepository.cs

@ -52,7 +52,7 @@ public class EfCoreIdentitySessionRepository : Volo.Abp.Identity.EntityFramework
.ToListAsync(GetCancellationToken(cancellationToken));
}
public async virtual Task DeleteAllAsync(
public async virtual Task DeleteAllSessionAsync(
string sessionId,
Guid? exceptSessionId = null,
CancellationToken cancellationToken = default)

Loading…
Cancel
Save