2015-08-05 2 views
18

Я пытаюсь заблокировать несколько логинов с одним и тем же пользователем в своем приложении.
Моя идея - обновить штамп безопасности при входе пользователя и добавить его в качестве претензии, а затем в каждом отдельном запросе, сравнивая штамп с куки-файлом с таковым в базе данных. Это, как я реализовал, что:Предотвращение нескольких логинов

 public virtual async Task<ActionResult> Login([Bind(Include = "Email,Password,RememberMe")] LoginViewModel model, string returnUrl) 
    { 
     if (!ModelState.IsValid) 
     { 
      return View(model); 
     } 

     SignInStatus result = 
      await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, false); 
     switch (result) 
     { 
      case SignInStatus.Success: 
       var user = UserManager.FindByEmail(model.Email); 
       var id = user.Id; 
       UserManager.UpdateSecurityStamp(user.Id); 
       var securityStamp = UserManager.FindByEmail(model.Email).SecurityStamp; 
       UserManager.AddClaim(id, new Claim("SecurityStamp", securityStamp)); 

Затем в конфигурации аутентификации Я добавил

 app.UseCookieAuthentication(new CookieAuthenticationOptions 
     { 
      AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, 
      LoginPath = new PathString("/Account/Login"), 
      Provider = new CookieAuthenticationProvider 
      { 
       OnValidateIdentity = ctx => 
       { 
        var ret = Task.Run(() => 
        { 
         Claim claim = ctx.Identity.FindFirst("SecurityStamp"); 
         if (claim != null) 
         { 
          var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())); 
          var user = userManager.FindById(ctx.Identity.GetUserId()); 

          // invalidate session, if SecurityStamp has changed 
          if (user != null && user.SecurityStamp != null && user.SecurityStamp != claim.Value) 
          { 
           ctx.RejectIdentity(); 
          } 
         } 
        }); 
        return ret; 
       } 
      } 

     }); 

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

+0

Я думаю, что ваш подход является слишком сложным. Я сделал подобное. Создайте статический класс со статическим списком 'CurrentUsers'. Когда пользователь входит в систему, проверьте этот список. Отклонить, если текущий. Сложное решение - это то, что событие означает удаление пользователей для «CurrentUsers». Очевидно, что выход из системы был причиной. Но вы хотите, чтобы пользователь сохранил файл cookie, но удалил их как текущий, когда сеансы запуска переработаны (соединение браузера завершается)? В этом случае в методе 'Session_OnEnd()' вы должны удалить пользователя из 'CurrentUsers'. –

ответ

19

Раствор несколько более простой, чем вы начали реализации. Но идея такая же: каждый раз, когда пользователь входит в систему, измените свою марку безопасности. И это приведет к аннулированию всех других сеансов входа в систему. Таким образом, мы научим пользователей не делиться своим паролем.

Я только что создал новое приложение MVC5 из стандартного шаблона VS2013 и успешно удалось реализовать то, что вы хотите сделать.

Метод входа. Вы должны изменить метку безопасности, прежде чем создавать аутентификации печенья, так как после того, как печенье установлено, вы не можете легко обновлять значение:

[HttpPost] 
[AllowAnonymous] 
[ValidateAntiForgeryToken] 
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) 
{ 
    if (!ModelState.IsValid) 
    { 
     return View(model); 
    } 


    // check if username/password pair match. 
    var loggedinUser = await UserManager.FindAsync(model.Email, model.Password); 
    if (loggedinUser != null) 
    { 
     // change the security stamp only on correct username/password 
     await UserManager.UpdateSecurityStampAsync(loggedinUser.Id); 
    } 

    // do sign-in 
    var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false); 
    switch (result) 
    { 
     case SignInStatus.Success: 
      return RedirectToLocal(returnUrl); 
     case SignInStatus.LockedOut: 
      return View("Lockout"); 
     case SignInStatus.RequiresVerification: 
      return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); 
     case SignInStatus.Failure: 
     default: 
      ModelState.AddModelError("", "Invalid login attempt."); 
      return View(model); 
    } 
} 

Таким образом, каждый Логин будет сделать обновление на записи пользователя с новый штамп безопасности. Обновление штампа безопасности - это только вопрос await UserManager.UpdateSecurityStampAsync(user.Id); - гораздо проще, чем вы предполагали.

Следующий шаг - проверить марку безопасности по каждому запросу. Вы уже нашли лучший крючок в Startup.Auth.cs, но вы снова слишком сложны. Рамки уже делает то, что вам нужно сделать, вам нужно настроить его немного:

app.UseCookieAuthentication(new CookieAuthenticationOptions 
{ 
    // other stuff 
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, 
    LoginPath = new PathString("/Account/Login"), 
    Provider = new CookieAuthenticationProvider 
    { 
     OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
      validateInterval: TimeSpan.FromMinutes(0), // <-- Note the timer is set for zero 
      regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) 
    } 
});    

интервал времени устанавливается на ноль - значит, каркас на каждом запросе будет сравнивать метку безопасности пользователя с базой данных. Если штамп в cookie не совпадает с штампом в базе данных, пользовательское auth-cookie выкидывается, прося их выйти из системы.

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


Full source in github


More information in a blog-post

+0

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

+0

@shaahin, что именно не работает? – trailmax

+0

Ну, он регистрирует меня, когда я ввожу свои учетные данные, но веб-сайт, который является шаблоном по умолчанию для визуальной студии, не показывает, что пользователь аутентифицирован. Он предполагает показывать имя пользователя, но он по-прежнему показывает логин и регистрацию ссылок. – Shahin

1

В прошлом я использовал IAuthorizationFilter и статическую коллекцию вошедшего пользователя для достижения этой цели:

public static class WebAppData 
{ 
    public static ConcurrentDictionary<string, AppUser> Users = new ConcurrentDictionary<string, AppUser>(); 
} 

public class AuthorisationAttribute : FilterAttribute, IAuthorizationFilter { 

    public void OnAuthorization(AuthorizationContext filterContext){ 

      ... 
      Handle claims authentication 
      ... 

      AppUser id = WebAppData.Users.Where(u=>u.Key ==userName).Select(u=>u.Value).FirstOrDefault(); 
      if (id == null){ 
       id = new AppUser {...} ; 
       id.SessionId = filterContext.HttpContext.Session.SessionID; 
       WebAppData.Users.TryAdd(userName, id); 
      } 
      else 
      { 
       if (id.SessionId != filterContext.HttpContext.Session.SessionID) 
       { 
         FormsAuthentication.SignOut(); 
         ... 
         return appropriate error response depending is it ajax request or not 
         ... 


       } 
      } 
    } 
} 

На выходе из системы:

WebAppData.Users.TryRemove(userName, out user) 
+3

Что происходит, когда пользователь закрывает браузер без выхода из системы? –

+0

'FormsAuthentication' несколько не применим к платформе Identity – trailmax

Смежные вопросы