From 38ef51cec5648ae93f7877fea81546f189e9b9a6 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 24 Feb 2026 09:10:22 +0800 Subject: [PATCH] Refactor token providers to include cancellation token support and update token handling logic --- .../AspNetCore/AbpChangeEmailTokenProvider.cs | 6 +++-- .../AbpEmailConfirmationTokenProvider.cs | 6 +++-- .../AbpPasswordResetTokenProvider.cs | 6 +++-- .../AbpSingleActiveTokenProvider.cs | 22 ++++++++++++++----- ...yUserManagerSingleActiveTokenExtensions.cs | 14 ++++++------ .../AbpSingleActiveTokenProviderTestBase.cs | 4 ++-- ...anagerSingleActiveTokenExtensions_Tests.cs | 12 +++++----- 7 files changed, 43 insertions(+), 27 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpChangeEmailTokenProvider.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpChangeEmailTokenProvider.cs index c938b6db67..344c188264 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpChangeEmailTokenProvider.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpChangeEmailTokenProvider.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.Identity; +using Volo.Abp.Threading; namespace Volo.Abp.Identity.AspNetCore; @@ -18,8 +19,9 @@ public class AbpChangeEmailTokenProvider : AbpSingleActiveTokenProvider IDataProtectionProvider dataProtectionProvider, IOptions options, ILogger> logger, - IIdentityUserRepository userRepository) - : base(dataProtectionProvider, options, logger, userRepository) + IIdentityUserRepository userRepository, + ICancellationTokenProvider cancellationTokenProvider) + : base(dataProtectionProvider, options, logger, userRepository, cancellationTokenProvider) { } } diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpEmailConfirmationTokenProvider.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpEmailConfirmationTokenProvider.cs index 2b758f1fc2..1f99543f8f 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpEmailConfirmationTokenProvider.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpEmailConfirmationTokenProvider.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.Identity; +using Volo.Abp.Threading; namespace Volo.Abp.Identity.AspNetCore; @@ -31,8 +32,9 @@ public class AbpEmailConfirmationTokenProvider : AbpSingleActiveTokenProvider IDataProtectionProvider dataProtectionProvider, IOptions options, ILogger> logger, - IIdentityUserRepository userRepository) - : base(dataProtectionProvider, options, logger, userRepository) + IIdentityUserRepository userRepository, + ICancellationTokenProvider cancellationTokenProvider) + : base(dataProtectionProvider, options, logger, userRepository, cancellationTokenProvider) { } } diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpPasswordResetTokenProvider.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpPasswordResetTokenProvider.cs index 95736bbffc..cc3c960804 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpPasswordResetTokenProvider.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpPasswordResetTokenProvider.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.Identity; +using Volo.Abp.Threading; namespace Volo.Abp.Identity.AspNetCore; @@ -18,8 +19,9 @@ public class AbpPasswordResetTokenProvider : AbpSingleActiveTokenProvider IDataProtectionProvider dataProtectionProvider, IOptions options, ILogger> logger, - IIdentityUserRepository userRepository) - : base(dataProtectionProvider, options, logger, userRepository) + IIdentityUserRepository userRepository, + ICancellationTokenProvider cancellationTokenProvider) + : base(dataProtectionProvider, options, logger, userRepository, cancellationTokenProvider) { } } diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProvider.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProvider.cs index 511b2f251a..aa6454085c 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProvider.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProvider.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; +using Volo.Abp.Threading; namespace Volo.Abp.Identity.AspNetCore; @@ -19,27 +20,36 @@ namespace Volo.Abp.Identity.AspNetCore; /// public abstract class AbpSingleActiveTokenProvider : DataProtectorTokenProvider { - public const string TokenHashSuffix = "_TokenHash"; + /// + /// The internal login provider name used to store token hashes in the user token table. + /// Using a bracketed name clearly distinguishes these internal entries from real external + /// login providers (e.g. Google, GitHub) stored in the same table. + /// + public const string InternalLoginProvider = "[AbpSingleActiveToken]"; protected IIdentityUserRepository UserRepository { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + protected AbpSingleActiveTokenProvider( IDataProtectionProvider dataProtectionProvider, IOptions options, ILogger> logger, - IIdentityUserRepository userRepository) + IIdentityUserRepository userRepository, + ICancellationTokenProvider cancellationTokenProvider) : base(dataProtectionProvider, options, logger) { UserRepository = userRepository; + CancellationTokenProvider = cancellationTokenProvider; } public override async Task GenerateAsync(string purpose, UserManager manager, IdentityUser user) { var token = await base.GenerateAsync(purpose, manager, user); - await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Tokens); + await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Tokens, CancellationTokenProvider.Token); var tokenHash = ComputeSha256Hash(token); - user.SetToken(Options.Name, purpose + TokenHashSuffix, tokenHash); + user.SetToken(InternalLoginProvider, Options.Name + ":" + purpose, tokenHash); await manager.UpdateAsync(user); @@ -53,9 +63,9 @@ public abstract class AbpSingleActiveTokenProvider : DataProtectorTokenProvider< return false; } - await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Tokens); + await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Tokens, CancellationTokenProvider.Token); - var storedHash = user.FindToken(Options.Name, purpose + TokenHashSuffix)?.Value; + var storedHash = user.FindToken(InternalLoginProvider, Options.Name + ":" + purpose)?.Value; if (storedHash == null) { return false; diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions.cs index 5fa2ce89b3..5b721241fd 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions.cs @@ -7,7 +7,7 @@ namespace Volo.Abp.Identity.AspNetCore; /// Provides extension methods on for invalidating /// single-active tokens managed by . /// These helpers live in the AspNetCore layer because they depend on -/// . +/// . /// public static class IdentityUserManagerSingleActiveTokenExtensions { @@ -17,8 +17,8 @@ public static class IdentityUserManagerSingleActiveTokenExtensions /// public static Task RemovePasswordResetTokenAsync(this IdentityUserManager manager, IdentityUser user) { - var name = UserManager.ResetPasswordTokenPurpose + AbpSingleActiveTokenProvider.TokenHashSuffix; - return manager.RemoveAuthenticationTokenAsync(user, manager.Options.Tokens.PasswordResetTokenProvider, name); + var name = manager.Options.Tokens.PasswordResetTokenProvider + ":" + UserManager.ResetPasswordTokenPurpose; + return manager.RemoveAuthenticationTokenAsync(user, AbpSingleActiveTokenProvider.InternalLoginProvider, name); } /// @@ -27,8 +27,8 @@ public static class IdentityUserManagerSingleActiveTokenExtensions /// public static Task RemoveEmailConfirmationTokenAsync(this IdentityUserManager manager, IdentityUser user) { - var name = UserManager.ConfirmEmailTokenPurpose + AbpSingleActiveTokenProvider.TokenHashSuffix; - return manager.RemoveAuthenticationTokenAsync(user, manager.Options.Tokens.EmailConfirmationTokenProvider, name); + var name = manager.Options.Tokens.EmailConfirmationTokenProvider + ":" + UserManager.ConfirmEmailTokenPurpose; + return manager.RemoveAuthenticationTokenAsync(user, AbpSingleActiveTokenProvider.InternalLoginProvider, name); } /// @@ -37,7 +37,7 @@ public static class IdentityUserManagerSingleActiveTokenExtensions /// public static Task RemoveChangeEmailTokenAsync(this IdentityUserManager manager, IdentityUser user, string newEmail) { - var name = UserManager.GetChangeEmailTokenPurpose(newEmail) + AbpSingleActiveTokenProvider.TokenHashSuffix; - return manager.RemoveAuthenticationTokenAsync(user, manager.Options.Tokens.ChangeEmailTokenProvider, name); + var name = manager.Options.Tokens.ChangeEmailTokenProvider + ":" + UserManager.GetChangeEmailTokenPurpose(newEmail); + return manager.RemoveAuthenticationTokenAsync(user, AbpSingleActiveTokenProvider.InternalLoginProvider, name); } } diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProviderTestBase.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProviderTestBase.cs index 37f9aec016..4f42391a05 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProviderTestBase.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProviderTestBase.cs @@ -39,7 +39,7 @@ public abstract class AbpSingleActiveTokenProviderTestBase : AbpIdentityAspNetCo /// Returns the token purpose used as the hash key prefix. protected abstract string GetPurpose(); - private string GetHashKey() => GetPurpose() + AbpSingleActiveTokenProvider.TokenHashSuffix; + private string GetTokenHashName() => GetProviderName() + ":" + GetPurpose(); [Fact] public async Task Generate_And_Verify_Token_Should_Succeed() @@ -103,7 +103,7 @@ public abstract class AbpSingleActiveTokenProviderTestBase : AbpIdentityAspNetCo // Overwrite with a non-hex string to simulate data corruption. user = await UserRepository.GetAsync(TestData.UserJohnId); - await UserManager.SetAuthenticationTokenAsync(user, GetProviderName(), GetHashKey(), "not-valid-hex!!!"); + await UserManager.SetAuthenticationTokenAsync(user, AbpSingleActiveTokenProvider.InternalLoginProvider, GetTokenHashName(), "not-valid-hex!!!"); user = await UserRepository.GetAsync(TestData.UserJohnId); diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions_Tests.cs index 93b06d0019..10beee48dd 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions_Tests.cs @@ -30,8 +30,8 @@ public class IdentityUserManagerSingleActiveTokenExtensions_Tests : AbpIdentityA using (var uow = UnitOfWorkManager.Begin()) { var user = await UserRepository.GetAsync(TestData.UserJohnId); - var providerName = UserManager.Options.Tokens.PasswordResetTokenProvider; - var tokenKey = UserManager.ResetPasswordTokenPurpose + AbpSingleActiveTokenProvider.TokenHashSuffix; + var providerName = AbpSingleActiveTokenProvider.InternalLoginProvider; + var tokenKey = UserManager.Options.Tokens.PasswordResetTokenProvider + ":" + UserManager.ResetPasswordTokenPurpose; await UserManager.SetAuthenticationTokenAsync(user, providerName, tokenKey, "hash-value"); (await UserManager.GetAuthenticationTokenAsync(user, providerName, tokenKey)).ShouldNotBeNull(); @@ -51,8 +51,8 @@ public class IdentityUserManagerSingleActiveTokenExtensions_Tests : AbpIdentityA using (var uow = UnitOfWorkManager.Begin()) { var user = await UserRepository.GetAsync(TestData.UserJohnId); - var providerName = UserManager.Options.Tokens.EmailConfirmationTokenProvider; - var tokenKey = UserManager.ConfirmEmailTokenPurpose + AbpSingleActiveTokenProvider.TokenHashSuffix; + var providerName = AbpSingleActiveTokenProvider.InternalLoginProvider; + var tokenKey = UserManager.Options.Tokens.EmailConfirmationTokenProvider + ":" + UserManager.ConfirmEmailTokenPurpose; await UserManager.SetAuthenticationTokenAsync(user, providerName, tokenKey, "hash-value"); (await UserManager.GetAuthenticationTokenAsync(user, providerName, tokenKey)).ShouldNotBeNull(); @@ -72,8 +72,8 @@ public class IdentityUserManagerSingleActiveTokenExtensions_Tests : AbpIdentityA using (var uow = UnitOfWorkManager.Begin()) { var user = await UserRepository.GetAsync(TestData.UserJohnId); - var providerName = UserManager.Options.Tokens.ChangeEmailTokenProvider; - var tokenKey = UserManager.GetChangeEmailTokenPurpose(NewEmail) + AbpSingleActiveTokenProvider.TokenHashSuffix; + var providerName = AbpSingleActiveTokenProvider.InternalLoginProvider; + var tokenKey = UserManager.Options.Tokens.ChangeEmailTokenProvider + ":" + UserManager.GetChangeEmailTokenPurpose(NewEmail); await UserManager.SetAuthenticationTokenAsync(user, providerName, tokenKey, "hash-value"); (await UserManager.GetAuthenticationTokenAsync(user, providerName, tokenKey)).ShouldNotBeNull();