Browse Source
- Session policy processing is transferred to the identity auth service - Add an identity authentication job module - Add an expired session cleaning jobpull/1375/head
21 changed files with 733 additions and 85 deletions
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,27 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net9.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Abp.Identity.Jobs</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.Identity.Jobs</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="LINGYUN\Abp\Identity\Jobs\Localization\Resources\*.json" /> |
|||
<EmbeddedResource Include="LINGYUN\Abp\Identity\Jobs\Localization\Resources\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.Domain\LINGYUN.Abp.Identity.Domain.csproj" /> |
|||
<ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.Notifications\LINGYUN.Abp.Identity.Notifications.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,29 @@ |
|||
using LINGYUN.Abp.BackgroundTasks; |
|||
using LINGYUN.Abp.Identity.Notifications; |
|||
using Volo.Abp.Identity.Localization; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace LINGYUN.Abp.Identity.Jobs; |
|||
|
|||
[DependsOn(typeof(AbpIdentityDomainModule))] |
|||
[DependsOn(typeof(AbpIdentityNotificationsModule))] |
|||
[DependsOn(typeof(AbpBackgroundTasksAbstractionsModule))] |
|||
public class AbpIdentityJobsModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpIdentityJobsModule>(); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Add<IdentityResource>() |
|||
.AddVirtualJson("/LINGYUN/Abp/Identity/Jobs/Localization/Resources"); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using LINGYUN.Abp.BackgroundTasks; |
|||
|
|||
namespace LINGYUN.Abp.Identity.Jobs; |
|||
public class IdentityJobDefinitionProvider : JobDefinitionProvider |
|||
{ |
|||
public override void Define(IJobDefinitionContext context) |
|||
{ |
|||
context.Add( |
|||
new JobDefinition( |
|||
InactiveIdentitySessionCleanupJob.Name, |
|||
typeof(InactiveIdentitySessionCleanupJob), |
|||
LocalizableStatic.Create("InactiveIdentitySessionCleanupJob"), |
|||
InactiveIdentitySessionCleanupJob.Paramters) |
|||
// TODO: 实现用户过期清理作业需要增加用户会话实体
|
|||
//new JobDefinition(
|
|||
// InactiveIdentityUserCleanupJob.Name,
|
|||
// typeof(InactiveIdentityUserCleanupJob),
|
|||
// LocalizableStatic.Create("InactiveIdentityUserCleanupJob"))
|
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
using LINGYUN.Abp.BackgroundTasks; |
|||
using LINGYUN.Abp.Identity.Session; |
|||
using Microsoft.Extensions.Logging; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Identity.Jobs; |
|||
/// <summary>
|
|||
/// 用户会话清理作业
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 此作业启用时,建议禁用 <see cref="IdentitySessionCleanupOptions.IsCleanupEnabled"/>
|
|||
/// </remarks>
|
|||
public class InactiveIdentitySessionCleanupJob : IJobRunnable |
|||
{ |
|||
public const string Name = "InactiveIdentitySessionCleanupJob"; |
|||
|
|||
#region Definition Paramters
|
|||
|
|||
public readonly static IReadOnlyList<JobDefinitionParamter> Paramters = |
|||
new List<JobDefinitionParamter> |
|||
{ |
|||
new JobDefinitionParamter( |
|||
PropertySessionInactiveDays, |
|||
LocalizableStatic.Create("DisplayName:SessionInactiveDays"), |
|||
LocalizableStatic.Create("Description:SessionInactiveDays")) |
|||
}; |
|||
|
|||
#endregion
|
|||
/// <summary>
|
|||
/// 不活跃会话保持时长, 单位天
|
|||
/// </summary>
|
|||
public const string PropertySessionInactiveDays = "SessionInactiveDays"; |
|||
|
|||
public async virtual Task ExecuteAsync(JobRunnableContext context) |
|||
{ |
|||
var logger = context.GetRequiredService<ILogger<InactiveIdentitySessionCleanupJob>>(); |
|||
var sessionStore = context.GetRequiredService<IIdentitySessionStore>(); |
|||
|
|||
var inactiveDays = context.GetOrDefaultJobData(PropertySessionInactiveDays, 30); |
|||
|
|||
logger.LogInformation("Prepare to clean up sessions that have been inactive for more than {inactiveDays} days.", inactiveDays); |
|||
|
|||
await sessionStore.RevokeAllAsync(TimeSpan.FromDays(inactiveDays)); |
|||
|
|||
logger.LogInformation($"Cleaned inactive user session."); |
|||
} |
|||
} |
|||
@ -0,0 +1,125 @@ |
|||
//using LINGYUN.Abp.BackgroundTasks;
|
|||
//using LINGYUN.Abp.Identity.Notifications;
|
|||
//using LINGYUN.Abp.Notifications;
|
|||
//using Microsoft.Extensions.Logging;
|
|||
//using System.Collections.Generic;
|
|||
//using System.Linq;
|
|||
//using System.Threading.Tasks;
|
|||
//using Volo.Abp.Domain.Repositories;
|
|||
//using Volo.Abp.Identity;
|
|||
//using Volo.Abp.Specifications;
|
|||
//using Volo.Abp.Timing;
|
|||
|
|||
//namespace LINGYUN.Abp.Identity.Jobs;
|
|||
|
|||
///// <summary>
|
|||
///// 用户清理作业
|
|||
///// </summary>
|
|||
///// <remarks>
|
|||
///// 清理长期未登录用户
|
|||
///// </remarks>
|
|||
//public class InactiveIdentityUserCleanupJob : IJobRunnable
|
|||
//{
|
|||
// public const string Name = "InactiveIdentityUserCleanupJob";
|
|||
|
|||
// #region Definition Paramters
|
|||
|
|||
// public readonly static IReadOnlyList<JobDefinitionParamter> Paramters =
|
|||
// new List<JobDefinitionParamter>
|
|||
// {
|
|||
// new JobDefinitionParamter(
|
|||
// PropertyUserInactiveCleanupDays,
|
|||
// LocalizableStatic.Create("DisplayName:UserInactiveCleanupDays"),
|
|||
// LocalizableStatic.Create("Description:UserInactiveCleanupDays")),
|
|||
// new JobDefinitionParamter(
|
|||
// PropertyUserInactiveNotifierDays,
|
|||
// LocalizableStatic.Create("DisplayName:UserInactiveNotifierDays"),
|
|||
// LocalizableStatic.Create("Description:UserInactiveNotifierDays")),
|
|||
// };
|
|||
|
|||
// #endregion
|
|||
// /// <summary>
|
|||
// /// 不活跃用户清理时间, 单位: 天
|
|||
// /// </summary>
|
|||
// public const string PropertyUserInactiveCleanupDays = "UserInactiveCleanupDays";
|
|||
// /// <summary>
|
|||
// /// 不活跃用户通知时间, 单位: 天
|
|||
// /// </summary>
|
|||
// public const string PropertyUserInactiveNotifierDays = "UserInactiveNotifierDays";
|
|||
|
|||
// public async virtual Task ExecuteAsync(JobRunnableContext context)
|
|||
// {
|
|||
// var logger = context.GetRequiredService<ILogger<InactiveIdentityUserCleanupJob>>();
|
|||
|
|||
// // 不活跃用户清理时间
|
|||
// var inactiveCleanupDays = context.GetOrDefaultJobData(PropertyUserInactiveCleanupDays, 30);
|
|||
// // 不活跃用户通知时间
|
|||
// var inactiveNotifierDays = context.GetOrDefaultJobData(PropertyUserInactiveNotifierDays, 180);
|
|||
|
|||
// var clock = context.GetRequiredService<IClock>();
|
|||
// var identityUserRepo = context.GetRequiredService<IIdentityUserRepository>();
|
|||
// var identityUserSessionRepo = context.GetRequiredService<IIdentitySessionRepository>();
|
|||
|
|||
// // 获取需要清理的用户集合
|
|||
// var specification = new ExpressionSpecification<IdentitySession>(
|
|||
// x => x.LastAccessed <= clock.Now.AddDays(-inactiveNotifierDays));
|
|||
|
|||
// using (identityUserSessionRepo.DisableTracking())
|
|||
// {
|
|||
// var inactiveUserCount = await identityUserSessionRepo.GetCountAsync(specification);
|
|||
// if (inactiveUserCount == 0)
|
|||
// {
|
|||
// logger.LogInformation("There are no inactive users to be notified.");
|
|||
// return;
|
|||
// }
|
|||
// // 不活跃用户会话集合
|
|||
// var inactiveUsers = await identityUserSessionRepo.GetListAsync(specification, maxResultCount: inactiveUserCount);
|
|||
|
|||
// // 直接清理的不活跃用户集合
|
|||
// var inactiveCleanupUsers = inactiveUsers.Where(x => x.LastAccessed <= clock.Now.AddDays(-inactiveCleanupDays));
|
|||
|
|||
// // 需要通知的不活跃用户
|
|||
// var inactiveNotifierUsers = inactiveUsers.ExceptBy(inactiveCleanupUsers.Select(x => x.Id), x => x.Id);
|
|||
// if (inactiveNotifierUsers.Count() != 0)
|
|||
// {
|
|||
// await SendInactiveUserNotifier(context, inactiveNotifierUsers);
|
|||
// }
|
|||
|
|||
// if (inactiveCleanupUsers.Count() != 0)
|
|||
// {
|
|||
// logger.LogInformation(
|
|||
// "Prepare to clean up {count} users who have been inactive for more than {inactiveCleanupDays} days.",
|
|||
// inactiveCleanupUsers.Count(),
|
|||
// inactiveCleanupDays);
|
|||
|
|||
// // 清理不活跃用户
|
|||
// await identityUserRepo.DeleteManyAsync(inactiveCleanupUsers.Select(x => x.UserId));
|
|||
// }
|
|||
// }
|
|||
// logger.LogInformation($"Cleaned inactive users.");
|
|||
// }
|
|||
|
|||
// /// <summary>
|
|||
// /// 发送不活跃用户清理通知
|
|||
// /// </summary>
|
|||
// /// <param name="context"></param>
|
|||
// /// <param name="userSessions"></param>
|
|||
// /// <returns></returns>
|
|||
// private async Task SendInactiveUserNotifier(JobRunnableContext context, IEnumerable<IdentitySession> userSessions)
|
|||
// {
|
|||
// // TODO: 完成不活跃用户清理通知
|
|||
// var notificationSender = context.GetRequiredService<INotificationSender>();
|
|||
|
|||
// var notificationTemplate = new NotificationTemplate(
|
|||
// IdentityNotificationNames.IdentityUser.CleaningUpInactiveUsers,
|
|||
// data: new Dictionary<string, object>
|
|||
// {
|
|||
|
|||
// });
|
|||
|
|||
// await notificationSender.SendNofiterAsync(
|
|||
// IdentityNotificationNames.IdentityUser.CleaningUpInactiveUsers,
|
|||
// notificationTemplate
|
|||
// );
|
|||
// }
|
|||
//}
|
|||
@ -0,0 +1,12 @@ |
|||
using Volo.Abp.Identity.Localization; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Identity.Jobs; |
|||
|
|||
internal static class LocalizableStatic |
|||
{ |
|||
public static ILocalizableString Create(string name) |
|||
{ |
|||
return LocalizableString.Create<IdentityResource>(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"InactiveIdentitySessionCleanupJob": "Inactive Session Cleanup Job", |
|||
"InactiveIdentityUserCleanupJob": "Inactive User Cleanup Job", |
|||
"DisplayName:SessionInactiveDays": "Duration of inactive conversation", |
|||
"Description:SessionInactiveDays": "Duration of inactive conversation, unit: days." |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"InactiveIdentitySessionCleanupJob": "不活跃会话清理作业", |
|||
"InactiveIdentityUserCleanupJob": "不活跃用户清理作业", |
|||
"DisplayName:SessionInactiveDays": "不活跃会话保持时长", |
|||
"Description:SessionInactiveDays": "不活跃会话保持时长,单位: 天." |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
using LINGYUN.Abp.Identity; |
|||
using LINGYUN.Abp.Identity.Session; |
|||
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.DistributedLocking; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Settings; |
|||
using Volo.Abp.Uow; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace LY.MicroService.AuthServer.Handlers; |
|||
/// <summary>
|
|||
/// 会话控制事件处理器
|
|||
/// </summary>
|
|||
public class IdentitySessionAccessEventHandler : |
|||
IDistributedEventHandler<IdentitySessionChangeAccessedEvent>, |
|||
IDistributedEventHandler<EntityCreatedEto<IdentitySessionEto>>, |
|||
IDistributedEventHandler<EntityDeletedEto<UserEto>>, |
|||
ITransientDependency |
|||
{ |
|||
public ILogger<IdentitySessionAccessEventHandler> Logger { protected get; set; } |
|||
protected ISettingProvider SettingProvider { get; } |
|||
protected IAbpDistributedLock DistributedLock { get; } |
|||
protected IIdentitySessionCache IdentitySessionCache { get; } |
|||
protected IIdentitySessionStore IdentitySessionStore { get; } |
|||
|
|||
public IdentitySessionAccessEventHandler( |
|||
ISettingProvider settingProvider, |
|||
IAbpDistributedLock distributedLock, |
|||
IIdentitySessionCache identitySessionCache, |
|||
IIdentitySessionStore identitySessionStore) |
|||
{ |
|||
SettingProvider = settingProvider; |
|||
DistributedLock = distributedLock; |
|||
IdentitySessionCache = identitySessionCache; |
|||
IdentitySessionStore = identitySessionStore; |
|||
|
|||
Logger = NullLogger<IdentitySessionAccessEventHandler>.Instance; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public async virtual Task HandleEventAsync(EntityCreatedEto<IdentitySessionEto> eventData) |
|||
{ |
|||
// 新会话创建时检查登录策略
|
|||
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityCreatedEto<IdentitySessionEto>)}"; |
|||
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) |
|||
{ |
|||
Logger.LogInformation($"Lock is acquired for {lockKey}"); |
|||
|
|||
if (handle == null) |
|||
{ |
|||
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); |
|||
return; |
|||
} |
|||
|
|||
await CheckConcurrentLoginStrategy(eventData.Entity); |
|||
} |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public async virtual Task HandleEventAsync(EntityDeletedEto<UserEto> eventData) |
|||
{ |
|||
// 用户被删除, 移除所有会话
|
|||
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityDeletedEto<UserEto>)}"; |
|||
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) |
|||
{ |
|||
Logger.LogInformation($"Lock is acquired for {lockKey}"); |
|||
|
|||
if (handle == null) |
|||
{ |
|||
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); |
|||
return; |
|||
} |
|||
|
|||
await IdentitySessionStore.RevokeAllAsync(eventData.Entity.Id); |
|||
} |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public async virtual Task HandleEventAsync(IdentitySessionChangeAccessedEvent eventData) |
|||
{ |
|||
// 会话访问更新
|
|||
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentitySessionChangeAccessedEvent)}"; |
|||
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) |
|||
{ |
|||
Logger.LogInformation($"Lock is acquired for {lockKey}"); |
|||
|
|||
if (handle == null) |
|||
{ |
|||
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); |
|||
return; |
|||
} |
|||
|
|||
var idetitySession = await IdentitySessionStore.FindAsync(eventData.SessionId); |
|||
if (idetitySession != null) |
|||
{ |
|||
if (!eventData.IpAddresses.IsNullOrWhiteSpace()) |
|||
{ |
|||
idetitySession.SetIpAddresses(eventData.IpAddresses.Split(",")); |
|||
} |
|||
idetitySession.UpdateLastAccessedTime(eventData.LastAccessed); |
|||
|
|||
await IdentitySessionStore.UpdateAsync(idetitySession); |
|||
} |
|||
else |
|||
{ |
|||
// 数据库中不存在会话, 清理缓存, 后续请求会话失效
|
|||
await IdentitySessionCache.RemoveAsync(eventData.SessionId); |
|||
} |
|||
} |
|||
} |
|||
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
using LINGYUN.Abp.Identity; |
|||
using LINGYUN.Abp.Identity.Session; |
|||
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.DistributedLocking; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Settings; |
|||
using Volo.Abp.Uow; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace LY.MicroService.IdentityServer.Handlers; |
|||
/// <summary>
|
|||
/// 会话控制事件处理器
|
|||
/// </summary>
|
|||
public class IdentitySessionAccessEventHandler : |
|||
IDistributedEventHandler<IdentitySessionChangeAccessedEvent>, |
|||
IDistributedEventHandler<EntityCreatedEto<IdentitySessionEto>>, |
|||
IDistributedEventHandler<EntityDeletedEto<UserEto>>, |
|||
ITransientDependency |
|||
{ |
|||
public ILogger<IdentitySessionAccessEventHandler> Logger { protected get; set; } |
|||
protected ISettingProvider SettingProvider { get; } |
|||
protected IAbpDistributedLock DistributedLock { get; } |
|||
protected IIdentitySessionCache IdentitySessionCache { get; } |
|||
protected IIdentitySessionStore IdentitySessionStore { get; } |
|||
|
|||
public IdentitySessionAccessEventHandler( |
|||
ISettingProvider settingProvider, |
|||
IAbpDistributedLock distributedLock, |
|||
IIdentitySessionCache identitySessionCache, |
|||
IIdentitySessionStore identitySessionStore) |
|||
{ |
|||
SettingProvider = settingProvider; |
|||
DistributedLock = distributedLock; |
|||
IdentitySessionCache = identitySessionCache; |
|||
IdentitySessionStore = identitySessionStore; |
|||
|
|||
Logger = NullLogger<IdentitySessionAccessEventHandler>.Instance; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public async virtual Task HandleEventAsync(EntityCreatedEto<IdentitySessionEto> eventData) |
|||
{ |
|||
// 新会话创建时检查登录策略
|
|||
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityCreatedEto<IdentitySessionEto>)}"; |
|||
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) |
|||
{ |
|||
Logger.LogInformation($"Lock is acquired for {lockKey}"); |
|||
|
|||
if (handle == null) |
|||
{ |
|||
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); |
|||
return; |
|||
} |
|||
|
|||
await CheckConcurrentLoginStrategy(eventData.Entity); |
|||
} |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public async virtual Task HandleEventAsync(EntityDeletedEto<UserEto> eventData) |
|||
{ |
|||
// 用户被删除, 移除所有会话
|
|||
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityDeletedEto<UserEto>)}"; |
|||
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) |
|||
{ |
|||
Logger.LogInformation($"Lock is acquired for {lockKey}"); |
|||
|
|||
if (handle == null) |
|||
{ |
|||
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); |
|||
return; |
|||
} |
|||
|
|||
await IdentitySessionStore.RevokeAllAsync(eventData.Entity.Id); |
|||
} |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public async virtual Task HandleEventAsync(IdentitySessionChangeAccessedEvent eventData) |
|||
{ |
|||
// 会话访问更新
|
|||
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentitySessionChangeAccessedEvent)}"; |
|||
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) |
|||
{ |
|||
Logger.LogInformation($"Lock is acquired for {lockKey}"); |
|||
|
|||
if (handle == null) |
|||
{ |
|||
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); |
|||
return; |
|||
} |
|||
|
|||
var idetitySession = await IdentitySessionStore.FindAsync(eventData.SessionId); |
|||
if (idetitySession != null) |
|||
{ |
|||
if (!eventData.IpAddresses.IsNullOrWhiteSpace()) |
|||
{ |
|||
idetitySession.SetIpAddresses(eventData.IpAddresses.Split(",")); |
|||
} |
|||
idetitySession.UpdateLastAccessedTime(eventData.LastAccessed); |
|||
|
|||
await IdentitySessionStore.UpdateAsync(idetitySession); |
|||
} |
|||
else |
|||
{ |
|||
// 数据库中不存在会话, 清理缓存, 后续请求会话失效
|
|||
await IdentitySessionCache.RemoveAsync(eventData.SessionId); |
|||
} |
|||
} |
|||
} |
|||
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue