Хорошо, это полное решение, которое я придумала, которое частично использует то, что предложил @jacktric, но также позволяет проверять штамп безопасности, если пароль пользователя был изменен в другом месте. Пожалуйста, дайте мне знать, если кто-то может порекомендовать какие-либо улучшения или увидеть какие-либо недостатки в моем решении.
Я удалил раздел OnValidateIdentity из раздела UseCookieAuthentication следующим образом:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
ctx.Response.Redirect(ctx.RedirectUri);
}
}
}
});
Затем у меня есть следующий IActionFilter, который зарегистрирован в FilterConfig.cs, который проверяет, вошел ли пользователь в систему (у меня есть части системы, к которым могут обращаться анонимные пользователи) и совпадает ли текущая метка безопасности с база данных. Эта проверка выполняется каждые 30 минут с использованием сеансов, чтобы выяснить, когда была последняя проверка.
public class CheckAuthenticationFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
try
{
// If not a child action, not an ajax request, not a RedirectResult and not a PartialViewResult
if (!filterContext.IsChildAction
&& !filterContext.HttpContext.Request.IsAjaxRequest()
&& !(filterContext.Result is RedirectResult)
&& !(filterContext.Result is PartialViewResult))
{
// Get current ID
string currentUserId = filterContext.HttpContext.User.Identity.GetUserId();
// If current user ID exists (i.e. it is not an anonymous function)
if (!String.IsNullOrEmpty(currentUserId))
{
// Variables
var lastValidateIdentityCheck = DateTime.MinValue;
var validateInterval = TimeSpan.FromMinutes(30);
var securityStampValid = true;
// Get instance of userManager
filterContext.HttpContext.GetOwinContext().Get<DbContext>().Database.Connection.ConnectionString = DbContext.GetConnectionString();
var userManager = filterContext.HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
// Find current user by ID
var currentUser = userManager.FindById(currentUserId);
// If "LastValidateIdentityCheck" session exists
if (HttpContext.Current.Session["LastValidateIdentityCheck"] != null)
DateTime.TryParse(HttpContext.Current.Session["LastValidateIdentityCheck"].ToString(), out lastValidateIdentityCheck);
// If first validation or validateInterval has passed
if (lastValidateIdentityCheck == DateTime.MinValue || DateTime.Now > lastValidateIdentityCheck.Add(validateInterval))
{
// Get current security stamp from logged in user
var currentSecurityStamp = filterContext.HttpContext.User.GetClaimValue("AspNet.Identity.SecurityStamp");
// Set whether security stamp valid
securityStampValid = currentUser != null && currentUser.SecurityStamp == currentSecurityStamp;
// Set LastValidateIdentityCheck session variable
HttpContext.Current.Session["LastValidateIdentityCheck"] = DateTime.Now;
}
// If current user doesn't exist or security stamp invalid then log them off
if (currentUser == null || !securityStampValid)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary { { "Controller", "Account" }, { "Action", "LogOff" }, { "Area", "" } });
}
}
}
}
catch (Exception ex)
{
// Log error
}
}
}
У меня есть следующие методы расширения для получения и обновления заявок для вошедшего в систему пользователя (взято из этого поста https://stackoverflow.com/a/32112002/1806809):
public static void AddUpdateClaim(this IPrincipal currentPrincipal, string key, string value)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return;
// Check for existing claim and remove it
var existingClaim = identity.FindFirst(key);
if (existingClaim != null)
identity.RemoveClaim(existingClaim);
// Add new claim
identity.AddClaim(new Claim(key, value));
// Set connection string - this overrides the default connection string set
// on "app.CreatePerOwinContext(DbContext.Create)" in "Startup.Auth.cs"
HttpContext.Current.GetOwinContext().Get<DbContext>().Database.Connection.ConnectionString = DbContext.GetConnectionString();
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
}
public static string GetClaimValue(this IPrincipal currentPrincipal, string key)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return null;
var claim = identity.Claims.FirstOrDefault(c => c.Type == key);
return claim.Value;
}
И, наконец, везде, где обновляется пароль пользователя, я вызываю следующее, это обновляет штамп безопасности для пользователя, чей пароль редактируется, и если это текущий пароль пользователя, который редактируется, то он обновляет утверждение securityStamp. для текущего пользователя, чтобы он не вышел из своего текущего сеанса в следующий раз, когда будет выполнена проверка достоверности:
// Update security stamp
UserManager.UpdateSecurityStamp(user.Id);
// If updating own password
if (GetCurrentUserId() == user.Id)
{
// Find current user by ID
var currentUser = UserManager.FindById(user.Id);
// Update logged in user security stamp (this is so their security stamp matches and they are not signed out the next time validity check is made in CheckAuthenticationFilter.cs)
User.AddUpdateClaim("AspNet.Identity.SecurityStamp", currentUser.SecurityStamp);
}