微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如果用户从IdentityServer4中的另一个浏览器/设备登录,如何从应用程序中检测和注销用户?

如何解决如果用户从IdentityServer4中的另一个浏览器/设备登录,如何从应用程序中检测和注销用户?

我已经在我的应用程序中实现了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);
    }
}

这在我们测试了几个用户的情况下仍然有效,但是该解决方案是否可以在有成千上万的用户登录系统的实际情况下工作?在全局范围内使用静态变量来存储会话信息是个好主意吗?使用此功能的潜在缺点是什么。请咨询。我们也欢迎新想法,如果有任何实现此功能的新方法,请告诉我们。

谢谢!

解决方法

免责声明:我对IS4没有实际经验。

您可能有充分的理由,但是我无法理解为什么您在验证当前最新登录名时会覆盖latestLogin会话的详细信息?

如果我没记错的话,该行将遍历您应用程序中的所有所有会话,在接下来的几行中您将有多个会话。

if (LoginSessions.Where(x => x.UserId.Contains(userId)).Count() > 0)

这确实是您不希望在希望扩展的应用程序中执行的操作。

不幸的是,我对IS4并不熟悉,我无法告诉您是否有可能通过利用其API完全解决此问题,相反,我可以为您提供实用建议。

您可以使用单独的集中式存储,可能性无穷无尽,但是memcached方面的东西是完美的。那么该算法就非常简单:

  1. 每当用户尝试登录时,都从存储中检索用户ID下存储的值。
  2. 如果存在,则为当前会话ID,然后在IS4中销毁它并继续。
  3. 创建新的登录会话,并将该会话ID存储在用户ID下的memcached中。

这样,对于给定的用户,会话永远不会超过1个,并且您已成功地将算法的复杂度从O(n)降低了,或更糟糕的是,降低到了O(1)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。