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())
.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
var scopes = Enumerable.Repeat<Guid?>(null, 1)
.Concat(activeTenants.Select(t => (Guid?)t.Id));
@ -76,29 +70,21 @@ public class PlatformSessionCleanupWorker : ISessionCleanupWorker, ITransientDep
foreach (var session in staleSessions)
{
await sessionRepo.DeleteAsync(session, cancellationToken: cancellationToken);
}
totalDeleted += staleSessions.Count;
if (staleSessions.Count > 0)
{
logger.LogInformation(
"IdentitySession temizliği: {Scope} için {Deleted} atıl oturum silindi.",
tenantId.HasValue ? $"Tenant [{tenantId}]" : "Host", staleSessions.Count);
"AbpSessions temizliği: TenantId:{TenantId}, UserId:{UserId}, SignedIn:{SignedIn}, Now:{Now}",
tenantId,
session.UserId,
session.SignedIn,
DateTime.UtcNow);
}
}
}
if (totalDeleted > 0)
{
logger.LogInformation(
"IdentitySession temizliği tamamlandı: toplam {Deleted} oturum silindi.",
totalDeleted);
}
}
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.MultiTenancy;
using Volo.Abp.Security.Claims;
using System.Linq;
namespace Sozsoft.Platform.Identity;
@ -20,14 +21,6 @@ public class PlatformSessionRevocationHandler :
IOpenIddictServerHandler<HandleRevocationRequestContext>,
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 ICurrentTenant currentTenant;
private readonly ILogger<PlatformSessionRevocationHandler> logger;
@ -51,12 +44,7 @@ public class PlatformSessionRevocationHandler :
if (string.IsNullOrEmpty(userId) || !Guid.TryParse(userId, out var userGuid)) return;
// Token'dan tenant ID'yi al.
var tenantIdStr = context.Principal?.GetClaim(AbpClaimTypes.TenantId);
Guid? tenantId = null;
if (!string.IsNullOrWhiteSpace(tenantIdStr) && Guid.TryParse(tenantIdStr, out var parsedTenantId))
{
tenantId = parsedTenantId;
}
var tenantId = ParseTenantId(context.Principal?.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId)?.Value);
try
{
@ -67,13 +55,13 @@ public class PlatformSessionRevocationHandler :
foreach (var session in sessions)
{
await sessionRepo.DeleteAsync(session);
}
if (sessions.Count > 0)
{
logger.LogInformation(
"Token revocation: {Count} IdentitySession silindi. UserId: {UserId}, TenantId: {TenantId}",
sessions.Count, userGuid, tenantId);
"AbpSessions temizliği: TenantId:{TenantId}, UserId:{UserId}, SignedIn:{SignedIn}, Now:{Now}",
tenantId,
session.UserId,
session.SignedIn,
DateTime.UtcNow);
}
}
}
@ -81,7 +69,9 @@ public class PlatformSessionRevocationHandler :
{
// Session temizleme hatası revocation akışını bloklamamalı.
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())
{
foreach (var session in existingSessions)
{
await identitySessionRepository.DeleteAsync(session);
}
}
else
{
if (!await platformSignInManager.CheckConcurrentLimitAsync(user))
{
throw new UserFriendlyException(PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit);
}
}
await InsertSessionAsync(user, request.ClientId);
@ -267,7 +271,7 @@ Your login code: {twoFactorToken}";
catch (UserFriendlyException) { throw; }
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)
{
var info = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
var tenantId = ParseTenantId(info.Principal?.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId)?.Value);
var userId = info.Principal?.GetClaim(OpenIddictConstants.Claims.Subject);
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)
{
// Concurrent login limiti refresh token için de geçerlidir; yeni oturum açılmadan önce eskiler temizlenir.
if (!await platformSignInManager.CheckConcurrentLimitAsync(refreshUser))
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)
{
await identitySessionRepository.DeleteAsync(session);
}
}
}
}
IActionResult result;
using (CurrentTenant.Change(tenantId))
{
result = await base.HandleRefreshTokenAsync(request);
}
var result = await base.HandleRefreshTokenAsync(request);
if (result is Microsoft.AspNetCore.Mvc.SignInResult && refreshUser != null)
{
try
if (result is Microsoft.AspNetCore.Mvc.SignInResult && refreshUser != null)
{
using (CurrentTenant.Change(refreshUser.TenantId))
try
{
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)

View file

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