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