ConcurrentUser düzeltmesi

This commit is contained in:
Sedat ÖZTÜRK 2026-04-28 13:04:07 +03:00
parent f6bc8b8473
commit 8234b1de2a
4 changed files with 43 additions and 63 deletions

View file

@ -54,12 +54,6 @@ public class PlatformSessionCleanupWorker : ISessionCleanupWorker, ITransientDep
.Where(t => t.GetIsActive()) .Where(t => t.GetIsActive())
.ToList(); .ToList();
logger.LogDebug(
"IdentitySession temizliği: host + {Count} aktif tenant işlenecek (eşik: {Cutoff:s}).",
activeTenants.Count, cutoff);
var totalDeleted = 0;
// Host (null) + tüm aktif tenant'lar // Host (null) + tüm aktif tenant'lar
var scopes = Enumerable.Repeat<Guid?>(null, 1) var scopes = Enumerable.Repeat<Guid?>(null, 1)
.Concat(activeTenants.Select(t => (Guid?)t.Id)); .Concat(activeTenants.Select(t => (Guid?)t.Id));
@ -76,29 +70,21 @@ public class PlatformSessionCleanupWorker : ISessionCleanupWorker, ITransientDep
foreach (var session in staleSessions) foreach (var session in staleSessions)
{ {
await sessionRepo.DeleteAsync(session, cancellationToken: cancellationToken); await sessionRepo.DeleteAsync(session, cancellationToken: cancellationToken);
}
totalDeleted += staleSessions.Count;
if (staleSessions.Count > 0)
{
logger.LogInformation( logger.LogInformation(
"IdentitySession temizliği: {Scope} için {Deleted} atıl oturum silindi.", "AbpSessions temizliği: TenantId:{TenantId}, UserId:{UserId}, SignedIn:{SignedIn}, Now:{Now}",
tenantId.HasValue ? $"Tenant [{tenantId}]" : "Host", staleSessions.Count); tenantId,
session.UserId,
session.SignedIn,
DateTime.UtcNow);
} }
} }
} }
if (totalDeleted > 0)
{
logger.LogInformation(
"IdentitySession temizliği tamamlandı: toplam {Deleted} oturum silindi.",
totalDeleted);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "IdentitySession temizliği sırasında hata oluştu."); logger.LogError(ex, "AbpSessions temizliği sırasında hata oluştu.");
} }
} }
} }

View file

@ -8,6 +8,7 @@ using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity; using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims; using Volo.Abp.Security.Claims;
using System.Linq;
namespace Sozsoft.Platform.Identity; namespace Sozsoft.Platform.Identity;
@ -20,14 +21,6 @@ public class PlatformSessionRevocationHandler :
IOpenIddictServerHandler<HandleRevocationRequestContext>, IOpenIddictServerHandler<HandleRevocationRequestContext>,
ITransientDependency ITransientDependency
{ {
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor
.CreateBuilder<HandleRevocationRequestContext>()
.UseScopedHandler<PlatformSessionRevocationHandler>()
.SetOrder(int.MaxValue - 10)
.SetType(OpenIddictServerHandlerType.Custom)
.Build();
private readonly IIdentitySessionRepository sessionRepo; private readonly IIdentitySessionRepository sessionRepo;
private readonly ICurrentTenant currentTenant; private readonly ICurrentTenant currentTenant;
private readonly ILogger<PlatformSessionRevocationHandler> logger; private readonly ILogger<PlatformSessionRevocationHandler> logger;
@ -51,12 +44,7 @@ public class PlatformSessionRevocationHandler :
if (string.IsNullOrEmpty(userId) || !Guid.TryParse(userId, out var userGuid)) return; if (string.IsNullOrEmpty(userId) || !Guid.TryParse(userId, out var userGuid)) return;
// Token'dan tenant ID'yi al. // Token'dan tenant ID'yi al.
var tenantIdStr = context.Principal?.GetClaim(AbpClaimTypes.TenantId); var tenantId = ParseTenantId(context.Principal?.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId)?.Value);
Guid? tenantId = null;
if (!string.IsNullOrWhiteSpace(tenantIdStr) && Guid.TryParse(tenantIdStr, out var parsedTenantId))
{
tenantId = parsedTenantId;
}
try try
{ {
@ -67,13 +55,13 @@ public class PlatformSessionRevocationHandler :
foreach (var session in sessions) foreach (var session in sessions)
{ {
await sessionRepo.DeleteAsync(session); await sessionRepo.DeleteAsync(session);
}
if (sessions.Count > 0)
{
logger.LogInformation( logger.LogInformation(
"Token revocation: {Count} IdentitySession silindi. UserId: {UserId}, TenantId: {TenantId}", "AbpSessions temizliği: TenantId:{TenantId}, UserId:{UserId}, SignedIn:{SignedIn}, Now:{Now}",
sessions.Count, userGuid, tenantId); tenantId,
session.UserId,
session.SignedIn,
DateTime.UtcNow);
} }
} }
} }
@ -81,7 +69,9 @@ public class PlatformSessionRevocationHandler :
{ {
// Session temizleme hatası revocation akışını bloklamamalı. // Session temizleme hatası revocation akışını bloklamamalı.
logger.LogWarning(ex, logger.LogWarning(ex,
"Token revocation sonrası IdentitySession temizliğinde hata. UserId: {UserId}", userId); "Token revocation sonrası AbpSessions temizliğinde hata. UserId: {UserId}", userId);
} }
} }
private static Guid? ParseTenantId(string value) => !string.IsNullOrWhiteSpace(value) && Guid.TryParse(value, out var id) ? id : null;
} }

View file

@ -253,12 +253,16 @@ Your login code: {twoFactorToken}";
if (existingSessions.Any()) if (existingSessions.Any())
{ {
foreach (var session in existingSessions) foreach (var session in existingSessions)
{
await identitySessionRepository.DeleteAsync(session); await identitySessionRepository.DeleteAsync(session);
}
} }
else else
{ {
if (!await platformSignInManager.CheckConcurrentLimitAsync(user)) if (!await platformSignInManager.CheckConcurrentLimitAsync(user))
{
throw new UserFriendlyException(PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit); throw new UserFriendlyException(PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit);
}
} }
await InsertSessionAsync(user, request.ClientId); await InsertSessionAsync(user, request.ClientId);
@ -267,7 +271,7 @@ Your login code: {twoFactorToken}";
catch (UserFriendlyException) { throw; } catch (UserFriendlyException) { throw; }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogWarning(ex, "IdentitySession yönetiminde hata oluştu. UserId: {UserId}", user.Id); Logger.LogWarning(ex, "AbpSessions yönetiminde hata oluştu. UserId: {UserId}", user.Id);
} }
} }
@ -275,50 +279,46 @@ Your login code: {twoFactorToken}";
protected override async Task<IActionResult> HandleRefreshTokenAsync(OpenIddictRequest request) protected override async Task<IActionResult> HandleRefreshTokenAsync(OpenIddictRequest request)
{ {
var info = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); var info = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
var tenantId = ParseTenantId(info.Principal?.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId)?.Value); var tenantId = ParseTenantId(info.Principal?.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId)?.Value);
var userId = info.Principal?.GetClaim(OpenIddictConstants.Claims.Subject);
IdentityUser refreshUser = null; IdentityUser refreshUser = null;
var userIdStr = info.Principal?.GetClaim(OpenIddictConstants.Claims.Subject);
if (!userIdStr.IsNullOrWhiteSpace() && Guid.TryParse(userIdStr, out var userId)) using (CurrentTenant.Change(tenantId))
{ {
using (CurrentTenant.Change(tenantId)) if (!userId.IsNullOrWhiteSpace() && Guid.TryParse(userId, out var userGuid))
{ {
refreshUser = await UserManager.FindByIdAsync(userId.ToString()); refreshUser = await UserManager.FindByIdAsync(userId);
if (refreshUser != null) if (refreshUser != null)
{ {
// Concurrent login limiti refresh token için de geçerlidir; yeni oturum açılmadan önce eskiler temizlenir.
if (!await platformSignInManager.CheckConcurrentLimitAsync(refreshUser)) if (!await platformSignInManager.CheckConcurrentLimitAsync(refreshUser))
throw new UserFriendlyException(PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit); throw new UserFriendlyException(PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit);
var existingSessions = await identitySessionRepository.GetListAsync(userId: userId); var existingSessions = await identitySessionRepository.GetListAsync(userId: userGuid);
foreach (var session in existingSessions) foreach (var session in existingSessions)
{
await identitySessionRepository.DeleteAsync(session); await identitySessionRepository.DeleteAsync(session);
}
} }
} }
}
IActionResult result; var result = await base.HandleRefreshTokenAsync(request);
using (CurrentTenant.Change(tenantId))
{
result = await base.HandleRefreshTokenAsync(request);
}
if (result is Microsoft.AspNetCore.Mvc.SignInResult && refreshUser != null) if (result is Microsoft.AspNetCore.Mvc.SignInResult && refreshUser != null)
{
try
{ {
using (CurrentTenant.Change(refreshUser.TenantId)) try
{ {
await InsertSessionAsync(refreshUser, request.ClientId); await InsertSessionAsync(refreshUser, request.ClientId);
} }
catch (Exception ex)
{
Logger.LogWarning(ex, "Refresh token sonrası IdentitySession oluşturulamadı. UserId: {UserId}", refreshUser.Id);
}
} }
catch (Exception ex)
{
Logger.LogWarning(ex, "Refresh token sonrası IdentitySession oluşturulamadı. UserId: {UserId}", refreshUser.Id);
}
}
return result; return result;
}
} }
private async Task InsertSessionAsync(IdentityUser user, string clientId) private async Task InsertSessionAsync(IdentityUser user, string clientId)

View file

@ -26,8 +26,10 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using OpenIddict.Server;
using OpenIddict.Server.AspNetCore; using OpenIddict.Server.AspNetCore;
using OpenIddict.Validation.AspNetCore; using OpenIddict.Validation.AspNetCore;
using static OpenIddict.Server.OpenIddictServerEvents;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Account.Web; using Volo.Abp.Account.Web;
using Volo.Abp.AspNetCore.Auditing; using Volo.Abp.AspNetCore.Auditing;
@ -85,6 +87,8 @@ public class PlatformHttpApiHostModule : AbpModule
{ {
server.SetAccessTokenLifetime(TimeSpan.FromMinutes(60)); server.SetAccessTokenLifetime(TimeSpan.FromMinutes(60));
server.SetRefreshTokenLifetime(TimeSpan.FromMinutes(90)); server.SetRefreshTokenLifetime(TimeSpan.FromMinutes(90));
server.AddEventHandler<HandleRevocationRequestContext>(builder =>
builder.UseScopedHandler<PlatformSessionRevocationHandler>());
}); });
builder.AddValidation(options => builder.AddValidation(options =>
{ {