如果用户从 IdentityServer4 中的另一个浏览器/设备登录,如何检测并从应用程序中注销用户?
Posted
技术标签:
【中文标题】如果用户从 IdentityServer4 中的另一个浏览器/设备登录,如何检测并从应用程序中注销用户?【英文标题】:How to detect and logout the user from an application if he logins from another browser/device in IdentityServer4? 【发布时间】:2021-01-30 18:06:13 【问题描述】:我已经在我的应用程序中实现了 IdentityServer4 SSO。 SSO 可以正常工作以及所有客户端的注销,但是有一个新要求,如果用户已经登录到应用程序并且如果他尝试再次登录(从不同的设备/浏览器),那么他应该自动注销以前的浏览器。我不明白这一点。如何实现这一点以及是否可以跟踪用户登录会话?
更新:-
我们尝试了以下方法,我们使用“Action”过滤属性将会话信息添加到全局静态变量中。这里我们存储了用户登录后的登录会话信息。
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);
这适用于我们为少数用户进行的测试,但该解决方案是否适用于有数千名用户登录系统的实际场景?全局使用静态变量来存储会话信息是个好主意吗?使用这个有什么潜在的缺点。请建议。我们也对新想法持开放态度,如果有任何实现此功能的新方法,请告诉我们。
谢谢!!!
【问题讨论】:
【参考方案1】:免责声明:我没有 IS4 的实践经验。
您可能有充分的理由,但我不明白为什么您在验证当前最新登录时会覆盖 latestLogin
会话的详细信息?
如果我没记错的话,这一行将循环遍历您应用程序中的 所有 个会话,您在后面的行中有多个类似的会话。
if (LoginSessions.Where(x => x.UserId.Contains(userId)).Count() > 0)
这确实是您不希望在您希望扩展的应用程序中做的事情。
很遗憾,我对 IS4 并不熟悉,我无法告诉你是否有可能完全利用它的 API 来解决这个问题,但我可以给你一些实用的建议。
您可以使用单独的集中式存储,可能性是无穷无尽的,但类似于memcached 的东西是完美的。那么算法就相当简单了:
-
每当用户尝试登录时,从存储中检索存储在用户 ID 下的值。
如果存在,那将是当前会话 ID,然后在 IS4 中将其销毁并继续。
创建新的登录会话并将会话 ID 存储在用户 ID 下的 memcached 中。
这样,给定用户的会话永远不会超过 1 个,并且您已成功将算法的复杂性从 O(n) 或更糟的情况降低到 O(1)。
【讨论】:
以上是关于如果用户从 IdentityServer4 中的另一个浏览器/设备登录,如何检测并从应用程序中注销用户?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 identityserver4 中没有用户名和密码的情况下从令牌端点获取令牌?