Asp.net mvc 如果用户从IdentityServer4中的其他浏览器/设备登录,如何检测并从应用程序注销?
我已经在我的应用程序中实现了IdentityServer4 SSO。SSO可以很好地为所有客户端注销,但是有一个新的要求,即如果用户已经登录到应用程序,并且如果他尝试再次登录(从不同的设备/浏览器),那么他应该自动从以前的浏览器注销。我不明白这一点。如何实现这一点,如果有可能跟踪用户登录会话 更新:- 我们尝试了以下方法,我们使用“Action”过滤器属性将会话信息添加到全局静态变量中。在这里,我们在用户登录后存储登录会话信息Asp.net mvc 如果用户从IdentityServer4中的其他浏览器/设备登录,如何检测并从应用程序注销?,asp.net-mvc,single-sign-on,identityserver4,logout,cookie-authentication,Asp.net Mvc,Single Sign On,Identityserver4,Logout,Cookie Authentication,我已经在我的应用程序中实现了IdentityServer4 SSO。SSO可以很好地为所有客户端注销,但是有一个新的要求,即如果用户已经登录到应用程序,并且如果他尝试再次登录(从不同的设备/浏览器),那么他应该自动从以前的浏览器注销。我不明白这一点。如何实现这一点,如果有可能跟踪用户登录会话 更新:- 我们尝试了以下方法,我们使用“Action”过滤器属性将会话信息添加到全局静态变量中。在这里,我们在用户登录后存储登录会话信息 private class LoginSession
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>();
私有类登录会话
{
内部字符串UserId{get;set;}
内部字符串SessionId{get;set;}
内部字符串AuthTime{get;set;}
内部日期时间偏移量AuthDateTime
{
得到
{
如果(!string.IsNullOrEmpty(AuthTime))
return DateTimeOffset.FromUnixTimeSeconds(long.Parse(AuthTime));
其他的
返回日期timeoffset.UtcNow;
}
}
}
私有静态列表登录会话=新列表();
在“ActionFilter”方法中,我们检查用户的会话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);
}
}
公共类SessionValidationAttribute:ActionFilterAttribute
{
公共重写任务OnActionExecutionAsync(ActionExecutionContext上下文,ActionExecutionDelegate下一步)
{
string action=context.RouteData.Values[“action”].ToString();
如果(!string.IsNullOrEmpty)(操作)&&
context.Controller.GetType().GetMethod(action).GetCustomAttributes(typeof(AllowAnonymousAttribute),true).长度==0
{
var声明=((ClaimsIdentity)((Microsoft.AspNetCore.Mvc.ControllerBase)context.Controller.User.Identity)声明;
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()值;
var authDateTime=DateTimeOffset.FromUnixTimeSeconds(long.Parse(authTime));
if(LoginSessions.Where(x=>x.UserId.Contains(UserId)).Count()>0)//如果已经登录
{
var latestLogin=LoginSessions.Where(x=>x.UserId==UserId).OrderByDescending(x=>x.AuthDateTime).First();
if(sessionId!=latestLogin.sessionId)
{
if(authDateTime>latestLogin.authDateTime)//使用新浏览器登录(会话)
{
latestLogin.SessionId=SessionId;//分配最新的SessionId
latestLogin.AuthTime=AuthTime;//分配最新的AuthTime
}
else if(authDateTimex.UserId==UserId&&x.SessionId!=latestLogin.SessionId);
context.Result=((Microsoft.AspNetCore.Mvc.ControllerBase)context.Controller)
.RedirectToAction(操作名称:“注销”,控制器名称:“主页”,
RouteValue:new{tenant=string.Empty,isRemoteError=false});
}
}
}
其他的
{
var newLogin=newloginsession(){UserId=UserId,SessionId=SessionId,AuthTime=AuthTime};
LoginSessions.Add(newLogin);
}
}
返回base.OnActionExecutionAsync(上下文,下一步);
}
}
我们对少数用户进行了测试,但该解决方案在有数千名用户登录到系统的实际场景中有效吗?全局使用静态变量存储会话信息是一个好主意吗?使用此功能的潜在缺点是什么?请给出建议。我们也乐于接受新的想法,如果有任何实现此功能的新方法,请告知我们
谢谢 免责声明:我没有使用IS4的实际经验 您可能有一个很好的理由,但我无法理解为什么在验证当前最新登录时覆盖
latestLogin
会话的详细信息
如果我没有弄错的话,这一行将在应用程序中的所有会话中循环,在接下来的几行中有多个类似的会话
if (LoginSessions.Where(x => x.UserId.Contains(userId)).Count() > 0)
这确实是您不希望在希望扩展的应用程序中执行的操作
不幸的是,我不熟悉IS4,我不能告诉你是否有可能完全通过使用它的API来解决这个问题,相反,我可以给你一些实用的建议
您可以使用单独的集中式存储,这种可能性是无限的,但类似的东西是完美的。那么算法就相当简单了: