Выйти из всех браузеров при изменении пароля

У меня есть страница Reset Password: введите описание изображения здесь

Когда пользователь заполняет детали и нажимает кнопку Reset Password. Вызывается следующий контроллер:

public ActionResult ResetPassword(ResetPassword model)
{
    ...
    return RedirectToAction("Logout");
}

Когда пользователь меняет свой пароль, он получает Logged Out из браузера. Однако, если они вошли в другой браузер, они остаются в другом браузере.

Я хочу вывести пользователя из всех браузеров, в которые они вошли, когда они меняют свой пароль.

Ответ 1

Итак, я вернулся домой и решил собрать код. Покажите мне код!!!

Я бы использовал обработчик, поэтому проверка всегда выполняется, когда пользователь сначала обращается к приложению, и он выполняется в одном месте для каждого доступа к методу доступа.

Идея заключается в том, что пользователь reset их пароль, приложение запишет у пользователя reset свой пароль и не выполнил вход в систему в первый раз и выйдет из системы.

user.HasResetPassword = true;
user.IsFirstLoginAfterPasswordReset = false;

Когда пользователь заходит, приложение проверяет, имел ли пользователь ранее reset свой пароль и теперь подписывается в первый раз. Если эти утверждения действительны, приложение обновляет свои записи, заявив, что у вас нет reset вашего пароля, и вы не подписываетесь в первый раз.

Шаг 1

Добавьте два свойства в модель ApplicationUser

введите описание изображения здесь

Шаг 2

Добавьте класс AuthHandler.cs в папку Модели с последующей реализацией. На этом этапе вы проверяете, имеет ли пользователь reset свой пароль и не выполнил вход в систему в первый раз с момента пароля reset. Если это так, перенаправите пользователя на логин.

введите описание изображения здесь

Шаг 3

В RouteConfig.cs вызовите AuthHandler так, чтобы он был вызван для каждого входящего HTTP-запроса в ваше приложение. введите описание изображения здесь

Шаг 4

В методе ResetPassword добавьте реализацию, как показано ниже. На этом этапе, когда пользователь имеет reset, их пароль обновляет свойства, чтобы сказать, они имеют reset свой пароль и не вошли в систему в первый раз. Обратите внимание, что пользователь также явно выписывается, когда они reset их пароль.

введите описание изображения здесь

Шаг 5

В методе Login добавьте реализацию ниже. На этом этапе, если пользователь успешно войдет в систему, убедитесь, что его пароль был reset, и они впервые вошли в систему, это false. Если все условия верны, обновите свойства в базе данных, чтобы свойства находились в состоянии готовности, когда пользователь сбрасывает пароль в будущем. Так вроде цикл, определяющий и обновляющий состояние пароля reset, и первые логины после сброса пароля.

введите описание изображения здесь

Наконец

Ваша таблица AspnetUsers должна выглядеть ниже

введите описание изображения здесь

Комментарии

Вот как я подхожу к нему. Я не тестировал его, поэтому вы можете изменить его, если столкнулись с исключением. Все они также жестко закодированы, чтобы показать подход к решению проблемы.

Ответ 2

Я видел, что вы используете ASP.NET Identity 2. То, что вы пытаетесь сделать, уже встроено. Все, что вам нужно сделать, это изменить SecurityStamp, и все предыдущие cookie-проверки подлинности перестали быть действительными.

После изменения пароля вам также необходимо изменить SecurityStamp:

await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword);
await UserManager.UpdateSecurityStampAsync(User.Identity.GetUserId());

Если вы хотите, чтобы пользователь оставался вошедшим в систему, вам необходимо переиздать новый файл cookie для подтверждения (signin):

    await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);

В противном случае пользователь/сеанс, который инициировал смену пароля, также выйдет из системы.

И чтобы вывести все остальные сеансы сразу, вам нужно опустить интервал проверки в config:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        // Enables the application to validate the security stamp when the user logs in.
        // This is a security feature which is used when you change a password or add an external login to your account.  
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
            validateInterval: TimeSpan.FromSeconds(1),
            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    }
});

Шаги для воспроизведения:

  • Создал новое веб-приложение Asp.Net в VS2015.
  • Выберите шаблон MVC.
  • Изменить App_Stat/Startup.Auth.cs, строка 34: изменить validateInterval: TimeSpan.FromMinutes(30) на validateInterval: TimeSpan.FromSeconds(1)
  • Редактировать контроллеры /ManageController.cs, строка 236: добавить вызов метода UserManager.UpdateSecurityStampAsync.
  • Запустите проект, создайте пользователя, войдите в систему, откройте другой браузер и войдите в систему.
  • Измените пароль, обновите страницу в другом браузере: вы должны выйти из системы.

Ответ 3

Аутентификация подлинности ASP.NET зависит от файлов cookie в браузере пользователя. Потому что вы используете два разных браузера для тестирования. У вас будет два разных файла cookie для проверки подлинности. До истечения срока действия файлов cookie пользователь все еще аутентифицирован. Вот почему вы получаете эти результаты.

Итак, вам придётся выполнить некоторую пользовательскую реализацию.

Например, всегда проверяйте, имеет ли пользователь reset пароль и еще не выполнил вход в систему в первый раз с новым паролем. Если они этого не сделали, выйдите из системы и перенаправляйтесь на логин. Когда они войдут в систему, будет создан новый файл cookie.

Ответ 4

Я смоделировал мой подход к этой статье из Github Blogs

Моделирование пользовательской сессии приложения

Они используют Hybrid Cookie Store / DB approach, используя ruby, но я портировал его в проект My ASP.Net MVC и отлично работает.

Пользователи могут видеть все остальные сеансы и отменять их, если это необходимо. Когда пользователь сбрасывает пароль, любые активные сеансы аннулируются.

Я использую ActionFilterAttribute на базовом контроллере для проверки активных сеансовых файлов cookie. Если файл cookie сеанса устарел, пользователь выходит из системы и перенаправляется для входа.

Ответ 5

На основе ответа CodeRealm...

Для тех, кто сталкивается с ситуацией, когда https-доступ к вашему приложению в браузере выдает исключение нулевого указателя (то есть ссылка на объект не устанавливается на экземпляр объекта.), это связано с тем, что в вашей базе данных могут быть существующие записи, HasResetPassWord и/или IsFirstLoginAfterPasswordReset имеет значение null. Запросы Http будут работать, но https-запросы потерпят неудачу, не знаю почему.

Решение: просто обновите базу данных вручную и укажите оба значения полей. Предпочтительно, false в обоих столбцах.

Ответ 6

Даже аутентификация ASP.NET четко говорит о необходимости вторичной проверки, чтобы подтвердить, что пользователь по-прежнему является активным зарегистрированным пользователем (например, мы можем заблокировать пользователя, пользователь может изменить свой пароль), билет на проверку подлинности форм не предлагает никакой защиты от этих вещей.

UserSession не имеет ничего общего с сеансом ASP.NET MVC, это просто имя здесь

Решением, которое я выполнил, является

  • Создайте таблицу UserSessions в базе данных с помощью UserSessionID (PK, Identity) UserID (FK) DateCreated, DateUpdated
  • FormsAuthenticationTicket имеет поле UserData, вы можете сохранить в нем UserSessionID.

Когда пользователь регистрируется в

public void DoLogin(){

     // do not call this ...
     // FormsAuthentication.SetAuthCookie(....

     DateTime dateIssued = DateTime.UtcNow;

     var sessionID = db.CreateSession(UserID);
     var ticket = new FormsAuthenticationTicket(
            userName,
            dateIssued,
            dateIssued.Add(FormsAuthentication.Timeout),
            iSpersistent,
            // userData
            sessionID.ToString());

     HttpCookie cookie = new HttpCookie(
         FormsAuthentication.CookieName,
         FormsAuthentication.Encrypt(ticket));
     cookie.Expires = ticket.Expires;
     if(FormsAuthentication.CookieDomain!=null)
         cookie.Domain = FormsAuthentication.CookieDomain;
     cookie.Path = FormsAuthentication.CookiePath;
     Response.Cookies.Add(cookie);

}

Авторизовать пользователя

Класс Global.asax позволяет подключиться к авторизованному

public void Application_Authorize(object sender, EventArgs e){
     var user = Context.User;
     if(user == null)   
         return;

     FormsIdentity formsIdentity = user.Identity as FormsIdentity;
     long userSessionID = long.Parse(formsIdentity.UserData);

     string cacheKey = "US-" + userSessionID;

     // caching to improve performance
     object result = HttpRuntime.Cache[cacheKey];
     if(result!=null){
         // if we had cached that user is alright, we return..
         return;
     }

     // hit the database and check if session is alright
     // If user has logged out, then all UserSessions should have been
     // deleted for this user
     UserSession session = db.UserSessions
           .FirstOrDefault(x=>x.UserSessionID == userSessionID);
     if(session != null){

          // update session and mark last date
          // this helps you in tracking and you
          // can also delete sessions which were not
          // updated since long time...
          session.DateUpdated = DateTime.UtcNow;
          db.SaveChanges();

          // ok user is good to login
          HttpRuntime.Cache.Add(cacheKey, "OK", 
               // set expiration for 5 mins
               DateTime.UtcNow.AddMinutes(5)..)

         // I am setting cache for 5 mins to avoid
         // hitting database for all session validation
         return;
     }

     // ok validation is wrong....


     throw new UnauthorizedException("Access denied");

}

Когда пользователь выходит из системы

public void Logout(){

    // get the ticket..
    FormsIdentity f = Context.User.Identity as FormsIdentity;
    long sessionID = long.Parse(f.UserData);

    // this will prevent cookie hijacking
    var session = db.UserSessions.First(x=>x.UserSessionID = sessionID);
    db.UserSession.Remove(session);
    db.SaveChanges();

    FormsAuthentication.Signout();
}

Когда пользователь меняет пароль или пользователь заблокирован или пользователь удален...

public void ChangePassword(){

    // get the ticket..
    FormsIdentity f = Context.User.Identity as FormsIdentity;
    long sessionID = long.Parse(f.UserData);

    // deleting Session will prevent all saved tickets from
    // logging in
    db.Database.ExecuteSql(
        "DELETE FROM UerSessions WHERE [email protected]",
        new SqlParameter("@SID", sessionID));
}