自定义声明在 AspNetCore Identity cookie 中一段时间后丢失

问题描述 投票:0回答:2

我看到一个多年前就已经被问过的问题,它解释了与使用 AspNet Identity 时丢失自定义声明相关的问题。不幸的是,那里提到的解决方案对我不起作用,因为我在 .NET 6 Blazor Server 应用程序上使用 AspNet Core Identity。

问题类似(在下面几点解释):

  1. 我在登录期间添加了一些声明(这些声明来自某些 API 调用,而不是来自 Identity 数据库,因此我在登录期间添加它们)。

  2. 我可以从 Blazor 组件访问它们。

  3. 它在 30% 的情况下工作正常,但在 70% 的情况下,cookie 会丢失我在登录期间添加的自定义声明,并且我的应用程序会遇到问题。我什至无法弄清楚这些声明何时丢失,因为在

    RevalidationInterval
    期间也没有发生这种情况,因为我用 1 分钟的 TimeSpan 对其进行了测试,并且当我多次测试时它至少在 5 分钟内运行良好。搜索了一堆答案,但没有找到 AspNet Core Identity 的正确答案。

这就是我的代码的样子:

  1. Program.cs 中的身份设置
    builder.Services
    .AddDefaultIdentity<IdentityUser>(options =>
    {
        options.SignIn.RequireConfirmedAccount = false;
        // Set Password options here if you'd like:
        options.Password.RequiredLength = 6;
    })
    .AddRoles<IdentityRole>()
    .AddUserManager<ADUserManager<IdentityUser>>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

    builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<ApplicationUser>>();

  1. 在 Login.cshtml.cs 中登录期间添加声明
    public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    {
        returnUrl ??= Url.Content("~/");
        if (!ModelState.IsValid) return Page();
    
        try
        {
            var adLoginResult = ADHelper.ADLogin(Input.Username, Input.Password);
            
            // Use adLoginResult data to populate custom claims here
            // Set additional info about the user using empTimeId and other custom claims
            var customClaims = new[]
            {
                new Claim("EmployeeTimeId", adLoginResult.TimeId)
            };
    
            // SignIn the user now
            await _signInManager.SignInWithClaimsAsync(user, Input.RememberMe, customClaims);
            return LocalRedirect(returnUrl);
        }
        catch (Exception ex)
        {
            ModelState.AddModelError(string.Empty, $"Login Failed. Error: {ex.Message}.");
            return Page();
        }
    }
  1. RevalidatingIdentityAuthenticationStateProvider.cs 中的重新验证方法
    public class RevalidatingIdentityAuthenticationStateProvider<TUser>
        : RevalidatingServerAuthenticationStateProvider where TUser : class
    {
        private readonly IServiceScopeFactory _scopeFactory;
        private readonly IdentityOptions _options;
    
        public RevalidatingIdentityAuthenticationStateProvider(
            ILoggerFactory loggerFactory,
            IServiceScopeFactory scopeFactory,
            IOptions<IdentityOptions> optionsAccessor)
            : base(loggerFactory)
        {
            _scopeFactory = scopeFactory;
            _options = optionsAccessor.Value;
        }
    
        protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(1); // More frequent for ease of testing
    
        protected override async Task<bool> ValidateAuthenticationStateAsync(AuthenticationState authenticationState, CancellationToken cancellationToken)
        {
            //Get the user manager from a new scope to ensure it fetches fresh data
            var scope = _scopeFactory.CreateScope();
    
            try
            {
                var userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>();
                return await ValidateSecurityTimeStampAsync(userManager, authenticationState.User);
            }
            finally
            {
                if(scope is IAsyncDisposable asyncDisposable)
                {
                    await asyncDisposable.DisposeAsync();
                }
                else
                {
                    scope.Dispose();
                }
            }
        }
    
        private async Task<bool> ValidateSecurityTimeStampAsync(UserManager<TUser> userManager, ClaimsPrincipal principal)
        {
            var user = await userManager.GetUserAsync(principal);
            if(user == null)
            {
                return false;
            }
            else if (!userManager.SupportsUserSecurityStamp)
            {
                return true;
            }
            else
            {
                var principalStamp = principal.FindFirstValue(_options.ClaimsIdentity.SecurityStampClaimType);
                var userStamp = await userManager.GetSecurityStampAsync(user);
                return principalStamp == userStamp;
            }
        }
    }
  1. 检索授权信息
    public class UserInfoService
    {
        private readonly AuthenticationStateProvider _authenticationStateProvider;
        private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
    
        public UserInfoService(AuthenticationStateProvider authenticationStateProvider, IDbContextFactory<ApplicationDbContext> dbContextFactory)
        {
            _authenticationStateProvider = authenticationStateProvider;
            _dbContextFactory = dbContextFactory;
        }
    
        public async Task<UserInfoFromAuthState?> GetCurrentUserInfoFromAuthStateAsync()
        {
            var userInfo = new UserInfoFromAuthState();
    
            var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
            if (authState == null ||
                authState.User == null ||
                authState.User.Identity == null ||
                !authState.User.Identity.IsAuthenticated)
            {
                return null;
            }
    
            userInfo.UserName = authState.User.Identity.Name!;
            
            // This comes out to be null after sometime a user has logged in
            userInfo.EmployeeTimeId = int.TryParse(authState.User.FindFirstValue("EmployeeTimeId", out var timeId) ? timeId : null;
    
            return userInfo;
        }
    }

这就是当我的自定义声明为空时我面临的问题:

"EmployeeTimeId"

c# asp.net-core asp.net-identity .net-6.0 blazor-server-side
2个回答
0
投票

最终通过在 aspnetcore github 存储库中提出问题解决了这个问题。 https://github.com/dotnet/aspnetcore/issues/49610

非常感谢@halter73的帮助!


事实证明,每当 SecurityStamp 重新验证时,主体就会被替换(默认间隔每 30 分钟发生一次)。因此,我通过在校长刷新时重新添加声明来解决此问题。我使用选项模式来完成此任务。

步骤1:

在 Identity 文件夹下添加一个名为

ConfigureSecurityStampOptions.cs
的新类(或任何您想要的位置):

第2步:

ConfigureSecurityStampOptions.cs
的内容应该是:

public class ConfigureSecurityStampOptions : IConfigureOptions<SecurityStampValidatorOptions>
{
    public void Configure(SecurityStampValidatorOptions options)
    {
        options.ValidationInterval = TimeSpan.FromMinutes(1);

        // When refreshing the principal, ensure custom claims that
        // might have been set with an external identity continue
        // to flow through to this new one.
        options.OnRefreshingPrincipal = refreshingPrincipal =>
        {
            ClaimsIdentity? newIdentity = refreshingPrincipal.NewPrincipal?.Identities.First();
            ClaimsIdentity? currentIdentity = refreshingPrincipal.CurrentPrincipal?.Identities.First();

            if (currentIdentity is not null && newIdentity is not null)
            {
                // Since this is refreshing an existing principal, we want to merge all claims.
                // Only work with claims in current identity that are not already present in the new identity with the same Type and Value.
                var currentClaimsNotInNewIdentity = currentIdentity.Claims.Where(c => !newIdentity.HasClaim(c.Type, c.Value));

                foreach (Claim claim in currentClaimsNotInNewIdentity)
                {
                    newIdentity.AddClaim(claim);
                }
            }

            return Task.CompletedTask;
        };
    }
}

步骤3:

Program.cs

注册
// To ensure custom claims are added to new identity when principal is refreshed.
builder.Services.ConfigureOptions<ConfigureSecurityStampOptions>();

完整源代码

https://github.com/affableashish/blazor-server-auth/tree/feature/AddClaimsDuringLogin


-1
投票

在 ASP.NET Core 中,登录期间添加的声明将添加到

ClaimsIdentity
并序列化到身份验证 cookie 中。如果 cookie 未设置为持久,则每当会话结束时声明都会丢失。如果 cookie 设置为持久,则当用户返回时,声明仍将存在,但只有在身份验证 cookie 失效或过期之前才会出现这种情况。

您的问题可能与

RevalidatingIdentityAuthenticationStateProvider
的重新验证间隔无关。它可能与身份验证 cookie 的生命周期及其管理方式有关。

以下是一些需要检查的事项:

检查会话和 Cookie 设置:根据您的环境和应用程序设置,会话或身份验证 Cookie 可能会比您预期的更早到期,从而导致声明丢失。检查您的 cookie 设置和会话超时设置,看看它们是否设置为对您的应用程序有意义的值。

由于滑动过期而丢失声明:在 ASP.NET Core 中,滑动过期会在每次发送回服务器时重置 cookie 过期时间,但前提是已经过了一半以上的时间。这有时会导致客户端的 cookie 过期,从而导致声明丢失。

要解决此问题,您可以增加身份验证 cookie 的过期时间或设置持久 cookie。

重新验证期间声明丢失: RevalidatingIdentityAuthenticationStateProvider 正在重新验证用户的安全标记。如果安全标记已更改,用户将被注销。因此,如果您的应用程序中的某些代码正在更改安全标记,则可能会导致您的用户注销,并且声明丢失。要检查这一点,请检查您的代码以查看是否有任何地方正在更新安全标记。

如果这些都没有帮助,创建一个小型的、可重现的示例来演示问题可能是个好主意。这将允许您系统地测试系统的每个部分并隔离导致问题的部分。它还会让其他人更容易帮助您调试问题。

请注意,在索赔中存储敏感信息可能并不是适合所有情况的最佳方法。声明对于不经常更改的授权数据非常有用,但对于经常更改的数据,通常最好在需要时直接从源中检索它。如果

EmployeeTimeId
值经常更改,您可能需要考虑将其存储在数据库中并在需要时检索它,而不是将其存储在声明中。

© www.soinside.com 2019 - 2024. All rights reserved.