Browse Source

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

feat: User password change cancellation session
dev
yx lin 1 day 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. 22
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs
  4. 13
      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. 6
      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>
public class IdentitySessionAccessEventHandler :
IDistributedEventHandler<IdentityUserSessionPasswordChangedEto>,
IDistributedEventHandler<IdentitySessionChangeAccessedEvent>,
IDistributedEventHandler<EntityCreatedEto<IdentitySessionEto>>,
IDistributedEventHandler<EntityDeletedEto<UserEto>>,
@ -50,11 +51,11 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityCreatedEto<IdentitySessionEto>)}";
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)
{
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}");
Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return;
}
@ -69,18 +70,43 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityDeletedEto<UserEto>)}";
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)
{
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}");
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)
{
@ -88,11 +114,11 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentitySessionChangeAccessedEvent)}";
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)
{
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}");
Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return;
}
@ -106,11 +132,15 @@ public class IdentitySessionAccessEventHandler :
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);
}
}
}

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);
}
}

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

@ -69,7 +69,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi
{
userName = "wxid-" + wehchatOpenId.OpenId.ToMd5().ToLower();
}
var userEmail = input.EmailAddress;//如果邮件地址不验证,随意写入一个
if (userEmail.IsNullOrWhiteSpace())
{
@ -102,7 +102,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi
await CheckNewUserPhoneNumberNotBeUsedAsync(input.PhoneNumber);
var securityTokenCacheKey = SecurityTokenCacheItem.CalculateSmsCacheKey(
input.PhoneNumber,
input.PhoneNumber,
UserTwoFactorTokenProviderConsts.PhoneNumberRegisterPurpose);
var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey);
var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1);
@ -124,8 +124,8 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi
await UserStore.SetSecurityStampAsync(tempNewUser, Guid.NewGuid().ToString("n"));
var code = await UserManager.GenerateUserTokenAsync(
tempNewUser,
UserTwoFactorTokenProviderConsts.PhoneNumberRegisterTokenProvider,
tempNewUser,
UserTwoFactorTokenProviderConsts.PhoneNumberRegisterTokenProvider,
UserTwoFactorTokenProviderConsts.PhoneNumberRegisterPurpose);
securityTokenCacheItem = new SecurityTokenCacheItem(code, tempNewUser.Id, await UserManager.GetSecurityStampAsync(tempNewUser));
@ -169,8 +169,8 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi
await UserStore.SetSecurityStampAsync(tempNewUser, securityTokenCacheItem.SecurityToken);
if (await UserManager.VerifyUserTokenAsync(
tempNewUser,
UserTwoFactorTokenProviderConsts.PhoneNumberRegisterTokenProvider,
tempNewUser,
UserTwoFactorTokenProviderConsts.PhoneNumberRegisterTokenProvider,
UserTwoFactorTokenProviderConsts.PhoneNumberRegisterPurpose,
input.Code))
{
@ -180,11 +180,12 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi
{
Name = input.Name ?? input.PhoneNumber
};
(await UserManager.CreateAsync(user)).CheckErrors();
(await UserManager.SetPhoneNumberAsync(user, input.PhoneNumber)).CheckErrors();
if (!input.Password.IsNullOrWhiteSpace())
{
(await UserManager.CreateAsync(user, input.Password)).CheckErrors();
(await UserManager.AddPasswordAsync(user, input.Password)).CheckErrors();
}
await UserStore.SetPhoneNumberConfirmedAsync(user, true);
@ -235,7 +236,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi
var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsResetPassword);
// 生成二次认证码
var code = await UserManager.GenerateUserTokenAsync(
user,
user,
TokenOptions.DefaultPhoneProvider,
UserTwoFactorTokenProviderConsts.PhoneResetPasswordPurpose);
// 发送短信验证码
@ -270,9 +271,9 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi
}
// 验证二次认证码
if (!await UserManager.VerifyUserTokenAsync(
user,
user,
TokenOptions.DefaultPhoneProvider,
UserTwoFactorTokenProviderConsts.PhoneResetPasswordPurpose,
UserTwoFactorTokenProviderConsts.PhoneResetPasswordPurpose,
input.Code))
{
// 验证码无效
@ -282,6 +283,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi
var resetPwdToken = await UserManager.GeneratePasswordResetTokenAsync(user);
// 重置密码
(await UserManager.ResetPasswordAsync(user, resetPwdToken, input.NewPassword)).CheckErrors();
// 移除缓存项
await SecurityTokenCache.RemoveAsync(securityTokenCacheKey);

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

@ -1,10 +1,11 @@
using System;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.Identity;
[Serializable]
public class IdentitySessionEto : IMultiTenant
public class IdentitySessionEto : EtoBase, IMultiTenant
{
public Guid Id { get; set; }
@ -30,14 +31,14 @@ public class IdentitySessionEto : IMultiTenant
}
public IdentitySessionEto(
Guid id,
Guid id,
string sessionId,
string device,
string device,
string deviceInfo,
Guid userId,
Guid userId,
string clientId,
string ipAddresses,
DateTime signedIn,
string ipAddresses,
DateTime signedIn,
DateTime? lastAccessed,
Guid? tenantId = null)
{

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 System.Collections.Generic;
using Volo.Abp.Identity;
using Volo.Abp.Mapperly;
@ -7,6 +8,23 @@ namespace LINGYUN.Abp.Identity;
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class IdentitySessionToIdentitySessionEtoMapper : MapperBase<IdentitySession, IdentitySessionEto>
{
[MapPropertyFromSource(nameof(IdentitySessionEto.Properties), Use = nameof(TryGetProperties))]
public override partial IdentitySessionEto Map(IdentitySession source);
[MapPropertyFromSource(nameof(IdentitySessionEto.Properties), Use = nameof(TryGetProperties))]
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;
}
}

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

@ -15,15 +15,15 @@ public interface IIdentitySessionManager
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task SaveSessionAsync(
ClaimsPrincipal claimsPrincipal,
ClaimsPrincipal claimsPrincipal,
CancellationToken cancellationToken = default);
/// <summary>
/// 撤销用户会话
/// </summary>
/// <param name="sessionId">会话id</param>
/// <param name="cancellation"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task RevokeSessionAsync(
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;
namespace LINGYUN.Abp.Identity.Session;
public class IdentitySessionManager : DomainService, IIdentitySessionManager
{
protected IDeviceInfoProvider DeviceInfoProvider { get; }
@ -99,9 +100,9 @@ public class IdentitySessionManager : DomainService, IIdentitySessionManager
public async virtual Task RevokeSessionAsync(
string sessionId,
CancellationToken cancellation = default)
CancellationToken cancellationToken = default)
{
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>
public class IdentitySessionAccessEventHandler :
IDistributedEventHandler<IdentityUserSessionPasswordChangedEto>,
IDistributedEventHandler<IdentitySessionChangeAccessedEvent>,
IDistributedEventHandler<EntityCreatedEto<IdentitySessionEto>>,
IDistributedEventHandler<EntityDeletedEto<UserEto>>,
@ -50,11 +51,11 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityCreatedEto<IdentitySessionEto>)}";
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)
{
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}");
Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return;
}
@ -69,18 +70,43 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityDeletedEto<UserEto>)}";
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)
{
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}");
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)
{
@ -88,11 +114,11 @@ public class IdentitySessionAccessEventHandler :
var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentitySessionChangeAccessedEvent)}";
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)
{
Logger.LogInformation($"Handle is null because of the locking for : {lockKey}");
Logger.LogDebug($"Handle is null because of the locking for : {lockKey}");
return;
}
@ -106,11 +132,15 @@ public class IdentitySessionAccessEventHandler :
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);
}
}
}

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