提问人:Jamie Stone 提问时间:11/16/2023 更新时间:11/16/2023 访问量:32
如何每隔一段时间刷新 .net Cookie 标识上的自定义声明?(IE:每半小时一次)
How do I refresh custom claims on a .net Cookie Identity on an interval? (IE: every half hour)
问:
我继承了一个使用 Microsoft Identity 进行身份验证和授权的 ASP.Net 站点。我们使用 SQL 作为身份数据库。
我不理解该网站的声明逻辑。我们正在使用一些自定义声明来指定用户可以访问哪些页面/产品。我希望对其进行设置,以便在用户登录后,当他们导航到新页面时,如果超过 30 分钟,则声明会刷新。我想这样做是为了避免在每个请求上实时检查我们的系统。基本上,在登录时缓存声明 30 分钟,如果超过 30 分钟,请刷新它们。
我不理解身份逻辑。我认为,在SecurityStampValidatorOptions上设置ValidationInterval将使“OnRefreshingPrincipal”在用户通过身份验证后超过半小时被调用。
在本地运行时,此方法永远不会命中。如果自上次刷新声明以来已超过 30 分钟,我应该或可以刷新声明,是否有其他方式?
我的示例(删除了一些重命名和公司特定的详细信息)
namespace MyWebsite
{
public static class SiteStartupExtensionMethods
{
public static void AddMyCustomIdentityAndCookieOptions(this IServiceCollection services, ConfigurationManager configurationManager)
{
// Add the identity database db context
services.AddDbContextFactory<MyIdentityDatabaseDbContext>(options =>
options.UseSqlServer(configurationManager.GetConnectionString("MyIdentityDatabaseDb")),
ServiceLifetime.Transient
);
// Add the identity options
services.Configure<IdentityOptions>(options =>
{
// Custom options removed for this post
});
// Add the identity / entity framework store
services.AddIdentity<MyCustomIdentityUser, IdentityRole>()
.AddEntityFrameworkStores<MyIdentityDatabaseDbContext>()
.AddDefaultTokenProviders()
.AddPasswordValidator<CommonPasswordValidator<MyCustomIdentityUser>>()
.AddPasswordValidator<UsernameAsPasswordValidator<MyCustomIdentityUser>>();
// Configure the cookie options
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"C:\....."))
.SetApplicationName("SharedCookieApp");
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Domain = ".myCustomWebsite.com";
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.HttpOnly = false;
options.Cookie.Name = ".AspNet.SharedCookie";
options.LoginPath = new PathString("/login");
options.AccessDeniedPath = new PathString("/access-denied");
options.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (context) =>
{
// Custom logic removed
return Task.CompletedTask;
}
};
});
// Set the security stamp options
services.Configure<SecurityStampValidatorOptions>(options =>
{
options.ValidationInterval = TimeSpan.FromMinutes(30);
options.OnRefreshingPrincipal = context =>
{
// This is not being called?
// Custom claims refreshing logic was here
return Task.FromResult(0);
};
});
// Add the claims factory
services.AddScoped<IUserClaimsPrincipalFactory<MyCustomIdentityUser>, ClaimsFactory>();
// Add the authentication state provider
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<MyCustomIdentityUser>>();
}
}
}
重新验证身份认证状态提供程序.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(30);
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 ValidateSecurityStampAsync(userManager, authenticationState.User);
}
finally
{
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}
private async Task<bool> ValidateSecurityStampAsync(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;
}
}
}
我试图将“ValidationInterval”更改并降低到一分钟,并且没有命中“OnRefreshingPrincipal”方法。
答:
SignInManager<TUser>.RefreshSignInAsync(TUser)
是你要找的方法。此方法将刷新 Cookie 声明。至于在特定的时间间隔内应用它,你可以实现一个最小的中间件。向保留“lastRefreshDate”的 cookie 添加声明,并在中间件中检查自上次刷新以来是否已经过去了 30 分钟。如果是这样,请刷新 cookie 并更新“lastRefreshDate”属性。
public class CookieRefreshMiddleware
{
private readonly RequestDelegate _next;
private readonly SignInManager<ApplicationUser> _signInManager;
public CookieRefreshMiddleware(RequestDelegate next, SignInManager<ApplicationUser> signInManager)
{
_next = next;
_signInManager = signInManager;
}
public async Task InvokeAsync(HttpContext context)
{
var user = context.User;
if (user.Identity.IsAuthenticated)
{
var lastRefreshDateClaim = user.FindFirst("lastRefreshDate");
if (lastRefreshDateClaim != null)
{
var lastRefreshDate = DateTime.Parse(lastRefreshDateClaim.Value);
if ((DateTime.UtcNow - lastRefreshDate).TotalMinutes >= 30)
{
var appUser = await _signInManager.UserManager.GetUserAsync(user);
appUser.Claims.Remove(lastRefreshDateClaim);
appUser.Claims.Add(new Claim("lastRefreshDate", DateTime.UtcNow.ToString()));
// Update the cookie
await _signInManager.RefreshSignInAsync(appUser);
}
}
}
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}
// In Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Other configurations...
app.UseMiddleware<CookieRefreshMiddleware>();
// Other configurations...
}
以下是注册中间件的方法。确保将其添加到授权中间件(行)之后。UseAuthorization
app.UseMiddleware<CookieRefreshMiddleware>();
不过,在我看来,您应该在更新用户角色时调用。那么您可能不需要这样的“定期更新”。SignInManager<TUser>.RefreshSignInAsync(TUser)
评论