防止多次登录

Sha*_*hin 23 cookies asp.net-mvc asp.net-identity

我试图在我的应用程序中使用相同的用户阻止多次登录.
我的想法是在用户登录时更新安全标记并将其添加为Claim,然后在每个请求中将Cookie中的标记与数据库中的标记进行比较.这就是我实现的方式:

        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));
Run Code Online (Sandbox Code Playgroud)

然后在身份验证配置中我添加了

        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;
                }
            }

        });
Run Code Online (Sandbox Code Playgroud)

如图所示,我试图将cookie中的声明与数据库中的声明进行比较,如果它们不相同则拒绝身份.
现在,每次用户登录时,安全标记都会更新,但用户的cookie中的值不同,我找不到原因?我怀疑是否新的更新安全标记不会存储在用户的cookie中?

tra*_*max 25

解决方案比您开始实施时更简单.但这个想法是一样的:每次用户登录时,都要更改他们的安全标记.这将使所有其他登录会话无效.这样就会教用户不要分享他们的密码.

我刚刚从标准的VS2013模板创建了一个新的MVC5应用程序,并成功地实现了您想要的功能.

登录方式.您需要在创建auth cookie之前更改安全标记,因为在设置cookie之后,您无法轻松更新值:

[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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这样,每次登录都会使用新的安全标记对用户记录进行更新.更新安全标记只是一个问题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))
    }
});            
Run Code Online (Sandbox Code Playgroud)

时间间隔设置为零 - 表示每个请求上的框架将比较用户的安全戳和数据库.如果cookie中的戳记与数据库中的戳记不匹配,则会抛出用户的auth-cookie,要求他们注销.

但是,请注意,这将在用户的每个HTTP请求上向您的数据库发出额外请求.在庞大的用户基础上,这可能会很昂贵,您可以将检查间隔时间增加到几分钟 - 这样可以减少对数据库的请求,但仍会传递有关不共享登录详细信息的消息.


github中的完整源代码


博客文章中的更多信息

  • @trailmax,在我的应用程序中的问题是会话没有被放弃,只有auth cookie过期(这是正确的).目前,我在为用户创建新会话之前在Login操作上添加了会话清理代码. (3认同)