committed by
GitHub
13 changed files with 508 additions and 33 deletions
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Identity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Identity; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class IdentityUserSessionPasswordChangedEto : IdentityUserPasswordChangedEto |
||||
|
{ |
||||
|
public string SessionId { get; set; } |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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…
Reference in new issue