Shiro 中的并发会话控制
Posted
技术标签:
【中文标题】Shiro 中的并发会话控制【英文标题】:Concurrent Session Control in Shiro 【发布时间】:2019-06-24 13:30:21 【问题描述】:我想将每个用户的并发会话数限制为 1。如果用户从第二个客户端/IP 登录,我想使他以前的会话无效(如果有的话)并为当前客户端创建一个新会话。因此,如果用户从第一个客户端发出另一个请求,则应该拒绝访问并重定向用户。
我在我的 Spring 启动应用程序中使用 Shiro。它是一个纯粹的 API 服务器,而不是 Web 应用程序。前端和后端是分开的。
Shiro 似乎没有开箱即用的会话限制支持。
我想知道我应该在哪里执行操作?
目前我有自己的
public class AuthRealm extends AuthorizingRealm
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
// Get user from db and return authentication info
不知道是不是一个干净的地方添加相应的逻辑?还是我应该在登录后的上一个会话中创建第二个客户端的会话?
我认为这样做可能更有意义
public class AuthRealm extends AuthorizingRealm
@Override
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException
// Credentials verified
// Invalidate previous session
Subject subject = SecurityUtils.getSubject();
Session existingSession = subject.getSession(false);
if (existingSession != null)
SecurityUtils.getSecurityManager().logout(subject);
existingSession.stop();
但事实证明,Shiro 创建了一个新主题,并绑定了一个新会话,每次登录,而不是每个用户。所以subject = SecurityUtils.getSubject()
将永远是一个全新的,并且不可能检索同一个用户然后检索其会话。有什么想法吗?
【问题讨论】:
您的 API 服务器是无状态的,还是希望客户端持有令牌/cookie?如果它是无状态的,并且您想将客户端限制为单个请求,您会发现某些 HTTP 客户端的工作方式具有挑战性,请参阅:medium.com/inloopx/… 此外,如果您启用了缓存,您的doGetAuthenticationInfo
方法将不会被调用,所以您会需要实施更高的东西。您最好的选择可能是某种过滤器。但是您也会增加使会话中间请求无效的风险
【参考方案1】:
经过大量研究,我发现最好的方法是写一个自定义SecurityManager
:
public class UniquePrincipalSecurityManager extends DefaultWebSecurityManager
private static final Logger logger = LoggerFactory.getLogger(AuthRealm.class);
@Override
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException
AuthenticationInfo info;
// Verify credentials
try
info = authenticate(token);
catch (AuthenticationException ae)
try
onFailedLogin(token, ae, subject);
catch (Exception e)
if (logger.isInfoEnabled())
logger.info("onFailedLogin method threw an " +
"exception. Logging and propagating original AuthenticationException.", e);
throw ae; //propagate
// Check the subject's existing session and stop it if present
DefaultWebSessionManager sm = (DefaultWebSessionManager) getSessionManager();
User loggedInUser = (User)(info.getPrincipals().getPrimaryPrincipal());
for (Session session : sm.getSessionDAO().getActiveSessions())
SimplePrincipalCollection p = (SimplePrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
User sessionUser = null;
if (p != null)
sessionUser = (User)(p.getPrimaryPrincipal());
if (sessionUser != null && loggedInUser.getId().equals(sessionUser.getId()))
session.stop();
sm.getSessionDAO().delete(session);
// Create new session for current login
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
请注意,只有在您验证凭据后才能删除上一个会话。
使用这种方法,如果您想将会话存储在另一个数据存储中,您还必须编写自己的会话管理器和 sessionDAO。
请注意,我之前将 Spring Session 与 redis 一起使用,但使用此自定义安全管理器,它不再起作用,因为会话管理将不再委托给 HttpSession
。
【讨论】:
你能显示 shiro.ini 的配置吗?或者如果您以编程方式设置自定义“SecurityManager”,您是如何做到的?我正在尝试像您在我的应用程序中所做的那样,但会话在登录后的后续访问中不会保持身份验证(登录运行正常,但在下一次访问中,subject.isAuthenticated() 返回 false,并且 getSessionDAO( ).getActiveSessions() 始终为空)。我的猜测是我在 shiro.ini 配置中遗漏了一些东西,但我不知道是什么以上是关于Shiro 中的并发会话控制的主要内容,如果未能解决你的问题,请参考以下文章
如果我知道 Apache Shiro 中的会话 ID 而不使用任何哈希映射,如何验证会话 ID?
SessionListener 中的 Shiro HttpSession?