防止在asp.net核心2中多次登录

时间:2017-11-22 15:33:29

标签: asp.net-identity core

美好的一天,

如何验证安全标记以防止 Asp.Net Core 2 中的单个用户多次登录,因为 IdentityOption 中没有 SecurityStampValidationInterval

提前致谢。

2 个答案:

答案 0 :(得分:0)

我使用了Microsoft.Extensions.Caching.Memory.MemoryCache来实现相同的功能。 (在缓存中存储用户名)

登录时(我们可以在验证密码之前执行此操作) 步骤1: 将Memory Cache作为DI用于控制器。

private IMemoryCache SiteCache = null;

public LoginHelper(IMemoryCache Cache)
{ 
  SiteCache = Cache; 
}

第2步:

在登录验证中,如果用户已存在于缓存中,请运行此检查。

private bool VerifyDuplicateLogin(string UserName, bool InsertKey)
{
    String sKey = UserName.ToLower();
    String sUser = Convert.ToString(SiteCache.Get<string>(sKey));

    if (string.IsNullOrEmpty(sUser))
    {
        if (InsertKey)
        {
            string cookieTimeout = appSettingsData.LoginCookieTimeout;
            int timeout = (string.IsNullOrEmpty(cookieTimeout)) ? 3 : int.Parse(cookieTimeout); 

            int sessionTimeOut = 5; // HttpContext.Current.Session.Timeout;

            sUser = string.Format("{0}^^^{1}", sKey, DateTime.Now.AddMinutes(sessionTimeOut).ToString("yyyy-MM-dd HH:mm:ss"));

            // No Cache item, so session is either expired or user is new sign-on,  Set the cache item and Session hit-test for this user
            TimeSpan SlidingTimeOut = new TimeSpan(0, 0, timeout, 0, 0); //(HttpContext.Current.Session.Timeout / 2) 

            MemoryCacheEntryOptions cacheOptions = new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = SlidingTimeOut
            };
            SiteCache.Set(sKey, sUser, cacheOptions); //OnCachedItemRemoved

            session.LoggedInUser = sKey;
        }

        //Let them in - redirect to main page, etc. 
        return false;
    }
    else
    {
        // cache item exists, means... "User already in" 
        return true;
    } 
}

第3步: 在注销时使用以下方法从缓存中删除用户名

public void RemoveLogin(string userName)
{
    //Clear the cache
    if ((!string.IsNullOrEmpty(userName)) && SiteCache != null)
    {
        String sUser = Convert.ToString(SiteCache.Get<string>(userName));
        if (!string.IsNullOrEmpty(sUser))
        {
            SiteCache.Remove(userName.ToLower());
            session.LoggedInUser = "";
        }
    }
}

由于我使用了内存缓存,因此每当服务器重置以及应用程序时,用户缓存也会重置,我们可以快速响应。

我们可以使用数据库实现相同的操作,暂时以相似的逻辑存储已登录的用户,但我觉得这种方法更快更轻松了。

这种方法的一个缺点是,如果用户关闭浏览器并希望立即重新登录,他将在用户已登录时收到响应(意味着他被锁定直到缓存密钥到期。(我们必须小心)而我们设置过期超时)

谢谢

答案 1 :(得分:0)

这建立在上面 Venkat pv 的答案之上。使用了一个中间件,通过它重置内存缓存中的会话计时器。我必须通过这条路线,因为从 Session Management in ASP Core 开始,通过会话中间件的每个 http 请求都会刷新会话超时。

我在此演示此功能 Github link。该解决方案已针对 ASP Core 2.2 和 3.1 进行了测试

显示了 http 请求的中间件:

public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseRequestHeaderMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestHeaderMiddleware>();
    }
}

public class RequestHeaderMiddleware
{
    private readonly RequestDelegate _next;
    LoginHelper _loginHelper;
    public RequestHeaderMiddleware(RequestDelegate next, LoginHelper loginHelper)
    {
        _next = next;
        _loginHelper = loginHelper;
    }

    /*
     * A middleware is used because each http request that passes through the Session Middleware resets the session time
     * https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-3.1
     */
    public async Task Invoke(HttpContext context)
    {
        //The CheckSingleLogin called in here would refresh the user time in the memory cache provided the user is logged in
        _loginHelper.CheckSingleLogin(context);

        await _next.Invoke(context);
    }
}

然后是一个带有内存缓存的 loginHelper 类:

 public class LoginHelper
{
    private IMemoryCache _siteCache = null;
    private readonly IConfiguration _configuration;


    public LoginHelper(IMemoryCache Cache, IConfiguration configuration)
    {
        _siteCache = Cache;
        _configuration = configuration;
    }

    /// <summary>
    /// this method is called from a middleware through which every request passes
    /// </summary>
    /// <param name="Context"></param>
    public void CheckSingleLogin(HttpContext Context)
    {
        var claim = Context.User;
        var username = claim?.FindFirst(ClaimTypes.Name)?.Value;

        //this is meant to refresh the session time in the cache memory
        IsDuplicateLogin(username);
    }

    /// <summary>
    /// Checks if the user is logged in already, resulting in duplicate login
    /// </summary>
    /// <param name="UserName"></param>
    /// <param name="IsFromLogin"></param>
    /// <returns></returns>
    public bool IsDuplicateLogin(string UserName, bool IsFromLogin = false)
    {
        if (string.IsNullOrEmpty(UserName))
            return false;

        String sKey = UserName.ToLower();
        String sUser = Convert.ToString(_siteCache.Get<string>(sKey));

        //returns true if this is from a login page AND the user already has session in memory
        if (!string.IsNullOrEmpty(sUser) && IsFromLogin)
            return true;

        if (!string.IsNullOrEmpty(sUser) || IsFromLogin)
        {

            var SessionTimeoutMinutes = _configuration["SessionTimeoutMinutes"] ?? "5";
            int timeout = int.Parse(SessionTimeoutMinutes);

            sUser = string.Format("{0} - {1} min from {2}", sKey, timeout, DateTime.Now.AddMinutes(timeout).ToString("yyyy-MM-dd HH:mm:ss"));

            TimeSpan slidingTimeout = new TimeSpan(0, 0, timeout, 0, 0);

            MemoryCacheEntryOptions cacheOptions = new MemoryCacheEntryOptions
            {
                // Keep in cache for this time, reset time if accessed again.
                //sliding expiration, rather than absolute expiration is used since session time is reset with each http request
                SlidingExpiration = slidingTimeout

            };

            _siteCache.Set(sKey, sUser, cacheOptions);

            //returns false if this comes from a login page 
            //at this point the user trying to log in does not have a session in memory
            //if not from a login page, then return true since the user has a session in memory
            return IsFromLogin ? false : true;
        }
        else
        {
            // return false since the user does not have a session in memory and he's not trying to login
            return false;
        }
    }

    /// <summary>
    /// Removes a user login from the memory when the user logs out
    /// </summary>
    /// <param name="UserName"></param>
    public void RemoveLogin(string UserName)
    {
        UserName = UserName?.ToLower();

        //Clear the cache
        if ((!string.IsNullOrEmpty(UserName)) && _siteCache != null)
        {
            var sUser = _siteCache.Get<string>(UserName);

            if (!string.IsNullOrEmpty(sUser))
            {
                _siteCache.Remove(UserName);
            }
        }
    }
}

在startup.cs中,将这个添加到ConfigureServices方法中:

        services.AddSingleton<LoginHelper>();

        var timeout = Configuration["SessionTimeoutMinutes"] ?? "5";
        double SessionTimeoutMinutes = Double.Parse(timeout);

        services.ConfigureApplicationCookie(options =>
        {
            options.Cookie.HttpOnly = true;
           // options.Cookie.Expiration = TimeSpan.FromMinutes(SessionTimeoutMinutes); //works only for Asp Core 2.2
            options.ExpireTimeSpan = TimeSpan.FromMinutes(SessionTimeoutMinutes);
            options.SlidingExpiration = true;
        });

        services.AddSession(options => {
            options.IdleTimeout = TimeSpan.FromMinutes(SessionTimeoutMinutes);
        });

在startup.cs的Configure方法中:

 //register the middleware so that the memory cache is refreshed with each request
  app.UseRequestHeaderMiddleware();

在 Login.cshtml.cs 中,将此添加为模型验证后的第一行。确保注入 LoginHelper。

 //this checks if the user is already logged in
  if (_loginHelper.IsDuplicateLogin(Input.Email, true))
  {
           ModelState.AddModelError(string.Empty, "You are already logged in");
           return Page();
  }

在 Logout.cshtml.cs 中,我们有这个

 //remove the user from the memory cache
  var user = await _signInManager.UserManager.GetUserAsync(User);
  _loginHelper.RemoveLogin(user.Email);