美好的一天,
如何验证安全标记以防止 Asp.Net Core 2 中的单个用户多次登录,因为 IdentityOption 中没有 SecurityStampValidationInterval 。
提前致谢。
答案 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);