committed by
GitHub
21 changed files with 721 additions and 114 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