Browse Source

Merge pull request #1543 from colinin/password-change-revoke-session

feat: User password change cancellation session
pull/1529/merge
yx lin 2 months ago
committed by GitHub
parent
commit
692dc0fa17
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 42
      aspnet-core/aspire/LINGYUN.Abp.MicroService.IdentityService/Handlers/IdentitySessionAccessEventHandler.cs
  2. 28
      aspnet-core/aspire/LINGYUN.Abp.MicroService.IdentityService/Handlers/IdentityUserChangedEventHandler.cs
  3. 4
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs
  4. 3
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IdentitySessionEto.cs
  5. 10
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IdentityUserSessionPasswordChangedEto.cs
  6. 110
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/AbpIdentityUserManager.cs
  7. 18
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IdentityDomainMappers.cs
  8. 4
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IIdentitySessionManager.cs
  9. 5
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionManager.cs
  10. 190
      aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/IdentitySessionAccessEventHandler.cs
  11. 27
      aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/IdentityUserChangedEventHandler.cs
  12. 42
      aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/Handlers/IdentitySessionAccessEventHandler.cs
  13. 28
      aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/Handlers/IdentityUserChangedEventHandler.cs

42
aspnet-core/aspire/LINGYUN.Abp.MicroService.IdentityService/Handlers/IdentitySessionAccessEventHandler.cs

@ -18,6 +18,7 @@ namespace LINGYUN.Abp.MicroService.IdentityService.Handlers;
/// 会话控制事件处理器 /// 会话控制事件处理器
/// </summary> /// </summary>
public class IdentitySessionAccessEventHandler : public class IdentitySessionAccessEventHandler :
IDistributedEventHandler<IdentityUserSessionPasswordChangedEto>,
IDistributedEventHandler<IdentitySessionChangeAccessedEvent>, IDistributedEventHandler<IdentitySessionChangeAccessedEvent>,
IDistributedEventHandler<EntityCreatedEto<IdentitySessionEto>>, IDistributedEventHandler<EntityCreatedEto<IdentitySessionEto>>,
IDistributedEventHandler<EntityDeletedEto<UserEto>>, IDistributedEventHandler<EntityDeletedEto<UserEto>>,
@ -50,11 +51,11 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityCreatedEto<IdentitySessionEto>)}"; var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityCreatedEto<IdentitySessionEto>)}";
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) await using (var handle = await DistributedLock.TryAcquireAsync(lockKey))
{ {
Logger.LogInformation($"Lock is acquired for {lockKey}"); Logger.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null) if (handle == null)
{ {
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return; return;
} }
@ -69,18 +70,43 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityDeletedEto<UserEto>)}"; var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityDeletedEto<UserEto>)}";
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) await using (var handle = await DistributedLock.TryAcquireAsync(lockKey))
{ {
Logger.LogInformation($"Lock is acquired for {lockKey}"); Logger.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null) if (handle == null)
{ {
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return; return;
} }
Logger.LogDebug("Due to the deletion of user {Id}, all sessions have been cancelled.", eventData.Entity.Id);
await IdentitySessionStore.RevokeAllAsync(eventData.Entity.Id); await IdentitySessionStore.RevokeAllAsync(eventData.Entity.Id);
} }
} }
[UnitOfWork]
public async virtual Task HandleEventAsync(IdentityUserSessionPasswordChangedEto eventData)
{
if (!eventData.SessionId.IsNullOrWhiteSpace() &&
Guid.TryParse(eventData.SessionId, out var exceptSessionId))
{
// 用户密码更新使会话过期
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentityUserSessionPasswordChangedEto)}";
await using var handle = await DistributedLock.TryAcquireAsync(lockKey);
Logger.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null)
{
Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return;
}
Logger.LogDebug("Due to the password update of user {Id}, all sessions have been revoked.", eventData.Id);
await IdentitySessionStore.RevokeAllAsync(eventData.Id, exceptSessionId);
}
}
[UnitOfWork] [UnitOfWork]
public async virtual Task HandleEventAsync(IdentitySessionChangeAccessedEvent eventData) public async virtual Task HandleEventAsync(IdentitySessionChangeAccessedEvent eventData)
{ {
@ -88,11 +114,11 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentitySessionChangeAccessedEvent)}"; var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentitySessionChangeAccessedEvent)}";
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) await using (var handle = await DistributedLock.TryAcquireAsync(lockKey))
{ {
Logger.LogInformation($"Lock is acquired for {lockKey}"); Logger.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null) if (handle == null)
{ {
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return; return;
} }
@ -106,11 +132,15 @@ public class IdentitySessionAccessEventHandler :
idetitySession.UpdateLastAccessedTime(eventData.LastAccessed); idetitySession.UpdateLastAccessedTime(eventData.LastAccessed);
await IdentitySessionStore.UpdateAsync(idetitySession); await IdentitySessionStore.UpdateAsync(idetitySession);
Logger.LogDebug("User session {SessionId} has been updated.", eventData.SessionId);
} }
else else
{ {
// 数据库中不存在会话, 清理缓存, 后续请求会话失效 // 数据库中不存在会话, 清理缓存, 后续请求会话失效
await IdentitySessionCache.RemoveAsync(eventData.SessionId); await IdentitySessionCache.RemoveAsync(eventData.SessionId);
Logger.LogWarning("User session {SessionId} is invalid. Remove all session caches.", eventData.SessionId);
} }
} }
} }

28
aspnet-core/aspire/LINGYUN.Abp.MicroService.IdentityService/Handlers/IdentityUserChangedEventHandler.cs

@ -0,0 +1,28 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;
using Volo.Abp.Uow;
using Volo.Abp.Users;
namespace LINGYUN.Abp.MicroService.IdentityService.Handlers;
public class IdentityUserChangedEventHandler :
IDistributedEventHandler<EntityCreatedEto<UserEto>>,
ITransientDependency
{
protected IdentityUserManager UserManager { get; }
public IdentityUserChangedEventHandler(IdentityUserManager userManager)
{
UserManager = userManager;
}
[UnitOfWork]
public async virtual Task HandleEventAsync(EntityCreatedEto<UserEto> eventData)
{
var user = await UserManager.GetByIdAsync(eventData.Entity.Id);
await UserManager.AddDefaultRolesAsync(user);
}
}

4
aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs

@ -180,11 +180,12 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi
{ {
Name = input.Name ?? input.PhoneNumber Name = input.Name ?? input.PhoneNumber
}; };
(await UserManager.CreateAsync(user)).CheckErrors();
(await UserManager.SetPhoneNumberAsync(user, input.PhoneNumber)).CheckErrors(); (await UserManager.SetPhoneNumberAsync(user, input.PhoneNumber)).CheckErrors();
if (!input.Password.IsNullOrWhiteSpace()) if (!input.Password.IsNullOrWhiteSpace())
{ {
(await UserManager.CreateAsync(user, input.Password)).CheckErrors(); (await UserManager.AddPasswordAsync(user, input.Password)).CheckErrors();
} }
await UserStore.SetPhoneNumberConfirmedAsync(user, true); await UserStore.SetPhoneNumberConfirmedAsync(user, true);
@ -282,6 +283,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi
var resetPwdToken = await UserManager.GeneratePasswordResetTokenAsync(user); var resetPwdToken = await UserManager.GeneratePasswordResetTokenAsync(user);
// 重置密码 // 重置密码
(await UserManager.ResetPasswordAsync(user, resetPwdToken, input.NewPassword)).CheckErrors(); (await UserManager.ResetPasswordAsync(user, resetPwdToken, input.NewPassword)).CheckErrors();
// 移除缓存项 // 移除缓存项
await SecurityTokenCache.RemoveAsync(securityTokenCacheKey); await SecurityTokenCache.RemoveAsync(securityTokenCacheKey);

3
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IdentitySessionEto.cs

@ -1,10 +1,11 @@
using System; using System;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.Identity; namespace LINGYUN.Abp.Identity;
[Serializable] [Serializable]
public class IdentitySessionEto : IMultiTenant public class IdentitySessionEto : EtoBase, IMultiTenant
{ {
public Guid Id { get; set; } public Guid Id { get; set; }

10
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IdentityUserSessionPasswordChangedEto.cs

@ -0,0 +1,10 @@
using System;
using Volo.Abp.Identity;
namespace LINGYUN.Abp.Identity;
[Serializable]
public class IdentityUserSessionPasswordChangedEto : IdentityUserPasswordChangedEto
{
public string SessionId { get; set; }
}

110
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/AbpIdentityUserManager.cs

@ -0,0 +1,110 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Settings;
using Volo.Abp.Threading;
using Volo.Abp.Users;
namespace LINGYUN.Abp.Identity;
[Dependency(ReplaceServices = true)]
[ExposeServices(
typeof(IdentityUserManager),
typeof(AbpIdentityUserManager),
typeof(UserManager<IdentityUser>))]
public class AbpIdentityUserManager : IdentityUserManager
{
public AbpIdentityUserManager(
IdentityUserStore store,
Volo.Abp.Identity.IIdentityRoleRepository roleRepository,
Volo.Abp.Identity.IIdentityUserRepository userRepository,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<IdentityUser> passwordHasher,
IEnumerable<IUserValidator<IdentityUser>> userValidators,
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<IdentityUserManager> logger,
ICancellationTokenProvider cancellationTokenProvider,
Volo.Abp.Identity.IOrganizationUnitRepository organizationUnitRepository,
ISettingProvider settingProvider,
IDistributedEventBus distributedEventBus,
IIdentityLinkUserRepository identityLinkUserRepository,
IDistributedCache<AbpDynamicClaimCacheItem> dynamicClaimCache,
IOptions<AbpMultiTenancyOptions> multiTenancyOptions,
ICurrentTenant currentTenant,
IDataFilter dataFilter)
: base(
store,
roleRepository,
userRepository,
optionsAccessor,
passwordHasher,
userValidators,
passwordValidators,
keyNormalizer,
errors,
services,
logger,
cancellationTokenProvider,
organizationUnitRepository,
settingProvider,
distributedEventBus,
identityLinkUserRepository,
dynamicClaimCache,
multiTenancyOptions,
currentTenant,
dataFilter)
{
}
public async override Task<IdentityResult> ResetPasswordAsync(IdentityUser user, string token, string newPassword)
{
var result = await base.ResetPasswordAsync(user, token, newPassword);
result.CheckErrors();
var currentUser = ServiceProvider.GetService<ICurrentUser>();
await DistributedEventBus.PublishAsync(new IdentityUserSessionPasswordChangedEto
{
Id = user.Id,
TenantId = user.TenantId,
Email = user.Email,
SessionId = currentUser?.FindSessionId(),
});
return result;
}
public async override Task<IdentityResult> ChangePasswordAsync(IdentityUser user, string currentPassword, string newPassword)
{
var result = await base.ChangePasswordAsync(user, currentPassword, newPassword);
result.CheckErrors();
var currentUser = ServiceProvider.GetService<ICurrentUser>();
await DistributedEventBus.PublishAsync(new IdentityUserSessionPasswordChangedEto
{
Id = user.Id,
TenantId = user.TenantId,
Email = user.Email,
SessionId = currentUser?.FindSessionId(),
});
return result;
}
}

18
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IdentityDomainMappers.cs

@ -1,4 +1,5 @@
using Riok.Mapperly.Abstractions; using Riok.Mapperly.Abstractions;
using System.Collections.Generic;
using Volo.Abp.Identity; using Volo.Abp.Identity;
using Volo.Abp.Mapperly; using Volo.Abp.Mapperly;
@ -7,6 +8,23 @@ namespace LINGYUN.Abp.Identity;
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class IdentitySessionToIdentitySessionEtoMapper : MapperBase<IdentitySession, IdentitySessionEto> public partial class IdentitySessionToIdentitySessionEtoMapper : MapperBase<IdentitySession, IdentitySessionEto>
{ {
[MapPropertyFromSource(nameof(IdentitySessionEto.Properties), Use = nameof(TryGetProperties))]
public override partial IdentitySessionEto Map(IdentitySession source); public override partial IdentitySessionEto Map(IdentitySession source);
[MapPropertyFromSource(nameof(IdentitySessionEto.Properties), Use = nameof(TryGetProperties))]
public override partial void Map(IdentitySession source, IdentitySessionEto destination); public override partial void Map(IdentitySession source, IdentitySessionEto destination);
[UserMapping(Default = false)]
private static Dictionary<string, string> TryGetProperties(IdentitySession source)
{
var properties = new Dictionary<string, string>();
if (source != null && source.ExtraProperties != null)
{
foreach (var property in source.ExtraProperties)
{
properties[property.Key] = property.Value.ToString();
}
}
return properties;
}
} }

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

@ -21,9 +21,9 @@ public interface IIdentitySessionManager
/// 撤销用户会话 /// 撤销用户会话
/// </summary> /// </summary>
/// <param name="sessionId">会话id</param> /// <param name="sessionId">会话id</param>
/// <param name="cancellation"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
Task RevokeSessionAsync( Task RevokeSessionAsync(
string sessionId, string sessionId,
CancellationToken cancellation = default); CancellationToken cancellationToken = default);
} }

5
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionManager.cs

@ -10,6 +10,7 @@ using Volo.Abp.Identity;
using Volo.Abp.Security.Claims; using Volo.Abp.Security.Claims;
namespace LINGYUN.Abp.Identity.Session; namespace LINGYUN.Abp.Identity.Session;
public class IdentitySessionManager : DomainService, IIdentitySessionManager public class IdentitySessionManager : DomainService, IIdentitySessionManager
{ {
protected IDeviceInfoProvider DeviceInfoProvider { get; } protected IDeviceInfoProvider DeviceInfoProvider { get; }
@ -99,9 +100,9 @@ public class IdentitySessionManager : DomainService, IIdentitySessionManager
public async virtual Task RevokeSessionAsync( public async virtual Task RevokeSessionAsync(
string sessionId, string sessionId,
CancellationToken cancellation = default) CancellationToken cancellationToken = default)
{ {
Logger.LogDebug($"Revoke user session for: {sessionId}"); Logger.LogDebug($"Revoke user session for: {sessionId}");
await IdentitySessionStore.RevokeAsync(sessionId, cancellation); await IdentitySessionStore.RevokeAsync(sessionId, cancellationToken: cancellationToken);
} }
} }

190
aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/IdentitySessionAccessEventHandler.cs

@ -0,0 +1,190 @@
using LINGYUN.Abp.Identity.Settings;
using Microsoft.Extensions.Logging.Abstractions;
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.Applications.Single.EventBus.Distributed;
/// <summary>
/// 会话控制事件处理器
/// </summary>
public class IdentitySessionAccessEventHandler :
IDistributedEventHandler<IdentityUserSessionPasswordChangedEto>,
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.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null)
{
Logger.LogDebug($"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.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null)
{
Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return;
}
Logger.LogDebug("Due to the deletion of user {Id}, all sessions have been cancelled.", eventData.Entity.Id);
await IdentitySessionStore.RevokeAllAsync(eventData.Entity.Id);
}
}
[UnitOfWork]
public async virtual Task HandleEventAsync(IdentityUserSessionPasswordChangedEto eventData)
{
if (!eventData.SessionId.IsNullOrWhiteSpace() &&
Guid.TryParse(eventData.SessionId, out var exceptSessionId))
{
// 用户密码更新使会话过期
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentityUserSessionPasswordChangedEto)}";
await using var handle = await DistributedLock.TryAcquireAsync(lockKey);
Logger.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null)
{
Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return;
}
Logger.LogDebug("Due to the password update of user {Id}, all sessions have been revoked.", eventData.Id);
await IdentitySessionStore.RevokeAllAsync(eventData.Id, exceptSessionId);
}
}
[UnitOfWork]
public async virtual Task HandleEventAsync(IdentitySessionChangeAccessedEvent eventData)
{
// 会话访问更新
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentitySessionChangeAccessedEvent)}";
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey))
{
Logger.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null)
{
Logger.LogDebug($"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);
Logger.LogDebug("User session {SessionId} has been updated.", eventData.SessionId);
}
else
{
// 数据库中不存在会话, 清理缓存, 后续请求会话失效
await IdentitySessionCache.RemoveAsync(eventData.SessionId);
Logger.LogWarning("User session {SessionId} is invalid. Remove all session caches.", 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;
}
}
}
}

27
aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Distributed/IdentityUserChangedEventHandler.cs

@ -0,0 +1,27 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;
using Volo.Abp.Uow;
using Volo.Abp.Users;
namespace LY.MicroService.Applications.Single.EventBus.Distributed;
public class IdentityUserChangedEventHandler :
IDistributedEventHandler<EntityCreatedEto<UserEto>>,
ITransientDependency
{
protected IdentityUserManager UserManager { get; }
public IdentityUserChangedEventHandler(IdentityUserManager userManager)
{
UserManager = userManager;
}
[UnitOfWork]
public async virtual Task HandleEventAsync(EntityCreatedEto<UserEto> eventData)
{
var user = await UserManager.GetByIdAsync(eventData.Entity.Id);
await UserManager.AddDefaultRolesAsync(user);
}
}

42
aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/Handlers/IdentitySessionAccessEventHandler.cs

@ -18,6 +18,7 @@ namespace LY.MicroService.AuthServer.Handlers;
/// 会话控制事件处理器 /// 会话控制事件处理器
/// </summary> /// </summary>
public class IdentitySessionAccessEventHandler : public class IdentitySessionAccessEventHandler :
IDistributedEventHandler<IdentityUserSessionPasswordChangedEto>,
IDistributedEventHandler<IdentitySessionChangeAccessedEvent>, IDistributedEventHandler<IdentitySessionChangeAccessedEvent>,
IDistributedEventHandler<EntityCreatedEto<IdentitySessionEto>>, IDistributedEventHandler<EntityCreatedEto<IdentitySessionEto>>,
IDistributedEventHandler<EntityDeletedEto<UserEto>>, IDistributedEventHandler<EntityDeletedEto<UserEto>>,
@ -50,11 +51,11 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityCreatedEto<IdentitySessionEto>)}"; var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityCreatedEto<IdentitySessionEto>)}";
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) await using (var handle = await DistributedLock.TryAcquireAsync(lockKey))
{ {
Logger.LogInformation($"Lock is acquired for {lockKey}"); Logger.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null) if (handle == null)
{ {
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return; return;
} }
@ -69,18 +70,43 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityDeletedEto<UserEto>)}"; var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityDeletedEto<UserEto>)}";
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) await using (var handle = await DistributedLock.TryAcquireAsync(lockKey))
{ {
Logger.LogInformation($"Lock is acquired for {lockKey}"); Logger.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null) if (handle == null)
{ {
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return; return;
} }
Logger.LogDebug("Due to the deletion of user {Id}, all sessions have been cancelled.", eventData.Entity.Id);
await IdentitySessionStore.RevokeAllAsync(eventData.Entity.Id); await IdentitySessionStore.RevokeAllAsync(eventData.Entity.Id);
} }
} }
[UnitOfWork]
public async virtual Task HandleEventAsync(IdentityUserSessionPasswordChangedEto eventData)
{
if (!eventData.SessionId.IsNullOrWhiteSpace() &&
Guid.TryParse(eventData.SessionId, out var exceptSessionId))
{
// 用户密码更新使会话过期
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentityUserSessionPasswordChangedEto)}";
await using var handle = await DistributedLock.TryAcquireAsync(lockKey);
Logger.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null)
{
Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return;
}
Logger.LogDebug("Due to the password update of user {Id}, all sessions have been revoked.", eventData.Id);
await IdentitySessionStore.RevokeAllAsync(eventData.Id, exceptSessionId);
}
}
[UnitOfWork] [UnitOfWork]
public async virtual Task HandleEventAsync(IdentitySessionChangeAccessedEvent eventData) public async virtual Task HandleEventAsync(IdentitySessionChangeAccessedEvent eventData)
{ {
@ -88,11 +114,11 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentitySessionChangeAccessedEvent)}"; var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentitySessionChangeAccessedEvent)}";
await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) await using (var handle = await DistributedLock.TryAcquireAsync(lockKey))
{ {
Logger.LogInformation($"Lock is acquired for {lockKey}"); Logger.LogDebug($"Lock is acquired for {lockKey}");
if (handle == null) if (handle == null)
{ {
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return; return;
} }
@ -106,11 +132,15 @@ public class IdentitySessionAccessEventHandler :
idetitySession.UpdateLastAccessedTime(eventData.LastAccessed); idetitySession.UpdateLastAccessedTime(eventData.LastAccessed);
await IdentitySessionStore.UpdateAsync(idetitySession); await IdentitySessionStore.UpdateAsync(idetitySession);
Logger.LogDebug("User session {SessionId} has been updated.", eventData.SessionId);
} }
else else
{ {
// 数据库中不存在会话, 清理缓存, 后续请求会话失效 // 数据库中不存在会话, 清理缓存, 后续请求会话失效
await IdentitySessionCache.RemoveAsync(eventData.SessionId); await IdentitySessionCache.RemoveAsync(eventData.SessionId);
Logger.LogWarning("User session {SessionId} is invalid. Remove all session caches.", eventData.SessionId);
} }
} }
} }

28
aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/Handlers/IdentityUserChangedEventHandler.cs

@ -0,0 +1,28 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;
using Volo.Abp.Uow;
using Volo.Abp.Users;
namespace LY.MicroService.AuthServer.Handlers;
public class IdentityUserChangedEventHandler :
IDistributedEventHandler<EntityCreatedEto<UserEto>>,
ITransientDependency
{
protected IdentityUserManager UserManager { get; }
public IdentityUserChangedEventHandler(IdentityUserManager userManager)
{
UserManager = userManager;
}
[UnitOfWork]
public async virtual Task HandleEventAsync(EntityCreatedEto<UserEto> eventData)
{
var user = await UserManager.GetByIdAsync(eventData.Entity.Id);
await UserManager.AddDefaultRolesAsync(user);
}
}
Loading…
Cancel
Save