我已经在我的应用程序中实现了 IdentityServer4 SSO。SSO 工作正常以及所有客户端的注销,但是有一个新要求,如果用户已经登录到应用程序并且如果他尝试再次登录(从不同的设备/浏览器),那么他应该自动注销以前的浏览器。我对此一无所知。如何实现这一点以及是否可以跟踪用户登录会话?
更新:-
我们尝试了以下方法,我们使用“操作”过滤器属性将会话信息添加到全局静态变量中。这里我们在用户登录后存储了登录会话信息。
private class LoginSession
{
internal string UserId { get; set; }
internal string SessionId { get; set; }
internal string AuthTime { get; set; }
internal DateTimeOffset AuthDateTime
{
get
{
if (!string.IsNullOrEmpty(AuthTime))
return DateTimeOffset.FromUnixTimeSeconds(long.Parse(AuthTime));
else
return DateTimeOffset.UtcNow;
}
}
}
private static List<LoginSession> LoginSessions = new List<LoginSession>();
在“操作过滤器”方法中,我们检查用户的会话 ID 是否已经存在。如果会话存在并且它的 SessionId 与声明会话 id 不匹配,那么我们检查会话的登录时间。如果登录时间小于当前登录时间,则用户退出系统,否则我们使用最新的会话 ID 和登录时间更新登录会话。由于第二次登录的这个工作流程,登录会话将被更新,因为登录时间总是大于保存的登录会话信息。对于旧的登录会话,用户将退出系统,因为登录时间总是小于更新的会话信息。
public class SessionValidationAttribute : ActionFilterAttribute
{
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
string action = context.RouteData.Values["action"].ToString();
if (!string.IsNullOrEmpty(action) &&
context.Controller.GetType().GetMethod(action).GetCustomAttributes(typeof(AllowAnonymousAttribute), true).Length == 0)
{
var claims = ((ClaimsIdentity)((Microsoft.AspNetCore.Mvc.ControllerBase)context.Controller).User.Identity).Claims;
var sessionId = claims.Where(x => x.Type == "sid").First().Value; // context.HttpContext.Request.Cookies.TryGetValue("idsrv.session", out var sessionId);
var userId = claims.Where(x => x.Type == "sub").First().Value;
var authTime = claims.Where(x => x.Type == "auth_time").First().Value;
var authDateTime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(authTime));
if (LoginSessions.Where(x => x.UserId.Contains(userId)).Count() > 0) // if already logged in
{
var latestLogin = LoginSessions.Where(x => x.UserId == userId).OrderByDescending(x => x.AuthDateTime).First();
if (sessionId != latestLogin.SessionId)
{
if(authDateTime > latestLogin.AuthDateTime) // login using new browser(session)
{
latestLogin.SessionId = sessionId; // assign latest sessionId
latestLogin.AuthTime = authTime; // assign latest authTime
}
else if (authDateTime < latestLogin.AuthDateTime) // login using old browser(session)
{
LoginSessions.RemoveAll(x => x.UserId == userId && x.SessionId!=latestLogin.SessionId);
context.Result = ((Microsoft.AspNetCore.Mvc.ControllerBase)context.Controller)
.RedirectToAction(actionName: "Logout", controllerName: "Home",
routeValues: new { tenant = string.Empty, isRemoteError = false });
}
}
}
else
{
var newLogin = new LoginSession() { UserId = userId, SessionId = sessionId, AuthTime = authTime };
LoginSessions.Add(newLogin);
}
}
return base.OnActionExecutionAsync(context, next);
}
}
这适用于我们为少数用户进行的测试,但该解决方案是否适用于有数千名用户登录系统的实际场景?全局使用静态变量存储会话信息是个好主意吗?使用这个有什么潜在的缺点。请建议。我们也对新想法持开放态度,如果有任何实现此功能的新方法,请告诉我们。
谢谢!!!